From e6e4183bf3e1678afd3e78abbb4fe3864eaa3050 Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Tue, 1 Nov 2022 17:59:08 +0200 Subject: [PATCH 01/12] add test for mixed loops (finite and infinite) --- testdata/p4_16_samples/parser-unroll-test9.p4 | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 testdata/p4_16_samples/parser-unroll-test9.p4 diff --git a/testdata/p4_16_samples/parser-unroll-test9.p4 b/testdata/p4_16_samples/parser-unroll-test9.p4 new file mode 100644 index 00000000000..01af72c25b8 --- /dev/null +++ b/testdata/p4_16_samples/parser-unroll-test9.p4 @@ -0,0 +1,96 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + pkt.extract(hdr.i); + hdr.i.counter = 0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 1; + transition select(hdr.i.index) { + 0: mixed_finite_loop; + 1: mixed_infinite_loop; + 2: infinite_loop; + 3: finite_loop; + default: reject; + } + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 1; + pkt.extract(hdr.h.next); + transition select (hdr.h.last.i1) { + 2 : accept; + default: finite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h.next); + transition select (hdr.h.last.i2) { + 1 : start_loops; + 2 : accept; + } + } + state mixed_infinite_loop { + transition select (hdr.i.counter) { + 3 : accept; + default : start_loops; + } + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 1; + transition select (hdr.i.counter) { + 3 : accept; + default : infinite_loop; + } + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; From 51859c7ceb267cc9f38e67eeed6a176aa0cd6758 Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Thu, 3 Nov 2022 18:09:02 +0200 Subject: [PATCH 02/12] update --- midend/parserUnroll.cpp | 47 +++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/midend/parserUnroll.cpp b/midend/parserUnroll.cpp index ec9b96f61e2..2d971d6c695 100644 --- a/midend/parserUnroll.cpp +++ b/midend/parserUnroll.cpp @@ -258,6 +258,23 @@ class ParserStateRewriter : public Transform { return true; } + /// Returns true for current id if indexes of the headers stack + /// are the same before previous call of the same parser state. + bool calledWithNoChanges(IR::ID id, const ParserStateInfo* state) { + CHECK_NULL(state); + const auto* prevState = state; + while (prevState->state->name.name != id.name) { + prevState = prevState->predecessor; + if (prevState == nullptr) { + return false; + } + } + if (prevState->predecessor != nullptr) { + prevState = prevState->predecessor; + } + return prevState->statesIndexes == state->statesIndexes; + } + /// Generated new state name IR::ID genNewName(IR::ID id) { if (isTerminalState(id)) @@ -266,13 +283,25 @@ class ParserStateRewriter : public Transform { cstring name = id.name; if (parserStructure->callsIndexes.count(id.name) && (state->scenarioStates.count(id.name) || parserStructure->reachableHSUsage(id, state))) { + // or self called with no extraction... + // in this case we need to use the same name as it was in previous call if (was_called(name, id)) return id; - index = parserStructure->callsIndexes[id.name]; - parserStructure->callsIndexes[id.name] = index + 1; - if (index + 1 > 0) { - index++; - id = IR::ID(id.name + std::to_string(index)); + if (calledWithNoChanges(id, state)) { + index = 0; + if (parserStructure->callsIndexes.count(id.name)) { + index = parserStructure->callsIndexes[id.name]; + } + if (index > 0) { + id = IR::ID(id.name + std::to_string(index)); + } + } else { + index = parserStructure->callsIndexes[id.name]; + parserStructure->callsIndexes[id.name] = index + 1; + if (index + 1 > 0) { + index++; + id = IR::ID(id.name + std::to_string(index)); + } } } else if (!parserStructure->callsIndexes.count(id.name)) { index = 0; @@ -771,7 +800,13 @@ class ParserSymbolicInterpreter { if (infLoop) { // Stop unrolling if it was an error. if (wasError) { - break; + IR::ID newName = getNewName(stateInfo); + if (newStates.count(newName) != 0) { + evaluateState(stateInfo, newStates); + } + wasError = false; + continue; + //break; } // don't evaluate successors anymore // generate call OutOfBound From cfbc35ac8b83dda7e483cc8abc33de209efbffbd Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Tue, 8 Nov 2022 15:33:43 +0200 Subject: [PATCH 03/12] fix. cpplint bugs --- midend/parserUnroll.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/midend/parserUnroll.cpp b/midend/parserUnroll.cpp index 2d971d6c695..e42f31e0ba4 100644 --- a/midend/parserUnroll.cpp +++ b/midend/parserUnroll.cpp @@ -262,7 +262,7 @@ class ParserStateRewriter : public Transform { /// are the same before previous call of the same parser state. bool calledWithNoChanges(IR::ID id, const ParserStateInfo* state) { CHECK_NULL(state); - const auto* prevState = state; + const auto* prevState = state; while (prevState->state->name.name != id.name) { prevState = prevState->predecessor; if (prevState == nullptr) { @@ -806,7 +806,6 @@ class ParserSymbolicInterpreter { } wasError = false; continue; - //break; } // don't evaluate successors anymore // generate call OutOfBound From 4d102b642898d9aced0fa6d63809ffea4cfd1a7f Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Wed, 9 Nov 2022 12:07:39 +0200 Subject: [PATCH 04/12] fix bug for loop with start --- midend/parserUnroll.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/midend/parserUnroll.cpp b/midend/parserUnroll.cpp index e42f31e0ba4..f03b727701d 100644 --- a/midend/parserUnroll.cpp +++ b/midend/parserUnroll.cpp @@ -262,7 +262,7 @@ class ParserStateRewriter : public Transform { /// are the same before previous call of the same parser state. bool calledWithNoChanges(IR::ID id, const ParserStateInfo* state) { CHECK_NULL(state); - const auto* prevState = state; + const auto* prevState = state; while (prevState->state->name.name != id.name) { prevState = prevState->predecessor; if (prevState == nullptr) { @@ -271,6 +271,9 @@ class ParserStateRewriter : public Transform { } if (prevState->predecessor != nullptr) { prevState = prevState->predecessor; + } else if (prevState == state) { + // The map should be empty if no next operators in a start. + return state->statesIndexes.empty(); } return prevState->statesIndexes == state->statesIndexes; } From d7dbe6ac07f0046ae7766acbc1147c9a1fc9814e Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Fri, 11 Nov 2022 15:03:25 +0200 Subject: [PATCH 05/12] update --- midend/parserUnroll.cpp | 1795 ++++++++++++++++++++------------------- midend/parserUnroll.h | 589 ++++++------- 2 files changed, 1203 insertions(+), 1181 deletions(-) diff --git a/midend/parserUnroll.cpp b/midend/parserUnroll.cpp index f03b727701d..ff23fff5bd1 100644 --- a/midend/parserUnroll.cpp +++ b/midend/parserUnroll.cpp @@ -1,888 +1,907 @@ -#include "parserUnroll.h" -#include "interpreter.h" -#include "lib/hash.h" -#include "lib/stringify.h" -#include "ir/ir.h" - -namespace P4 { - -StackVariable::StackVariable(const IR::Expression* expr) : variable(expr) { - CHECK_NULL(expr); - BUG_CHECK(repOk(expr), "Invalid stack variable %1%", expr); - variable = expr; -} - -bool StackVariable::repOk(const IR::Expression* expr) { - // Only members and path expression can be stack variables. - const auto* member = expr->to(); - if (member == nullptr) { - return expr->is(); - } - - // A member is a stack variable if it is qualified by a PathExpression or if its qualifier is a - // stack variable. - return member->expr->is() || repOk(member->expr); -} - -bool StackVariable::operator==(const StackVariable& other) const { - // Delegate to IR's notion of equality. - return variable->equiv(*other.variable); -} - -size_t StackVariableHash::operator()(const StackVariable& var) const { - // hash for path expression. - if (const auto* path = var.variable->to()) { - return Util::Hash::fnv1a(path->path->name.name); - } - const IR::Member* curMember = var.variable->to(); - std::vector h; - while (curMember) { - h.push_back(Util::Hash::fnv1a(curMember->member.name)); - if (auto* path = curMember->expr->to()) { - h.push_back(Util::Hash::fnv1a(path->path->name)); - break; - } - curMember = curMember->expr->checkedTo(); - } - return Util::Hash::fnv1a(h.data(), sizeof(size_t) * h.size()); -} - -/// The main class for parsers' states key for visited checking. -struct VisitedKey { - cstring name; // name of a state. - StackVariableMap indexes; // indexes of header stacks. - - VisitedKey(cstring name, StackVariableMap& indexes) - : name(name), indexes(indexes) {} - - explicit VisitedKey(const ParserStateInfo* stateInfo) { - CHECK_NULL(stateInfo); - name = stateInfo->state->name.name; - indexes = stateInfo->statesIndexes; - } - - /// Checks of two states to be less. If @a name < @a e.name then it returns @a true. - /// if @a name > @a e.name then it returns false. - /// If @a name is equal @a e.name then it checks the values of the headers stack indexes. - /// For both map of header stacks' indexes it creates map of pairs of values for each - /// header stack index. - /// If some indexes are missing then it cosiders them as -1. - /// Next, it continues checking each pair in a intermidiate map with the same approach - /// as for @a name and @a e.name. - bool operator<(const VisitedKey& e) const { - if (name < e.name) - return true; - if (name > e.name) - return false; - std::unordered_map, StackVariableHash > mp; - for (auto& i1 : indexes) { - mp.emplace(i1.first, std::make_pair(i1.second, -1)); - } - for (auto& i2 : e.indexes) { - auto ref = mp.find(i2.first); - if (ref == mp.end()) - mp.emplace(i2.first, std::make_pair(-1, i2.second)); - else - ref->second.second = i2.second; - } - for (auto j : mp) { - if (j.second.first < j.second.second) - return true; - if (j.second.first > j.second.second) - return false; - } - return false; - } -}; - -/** - * Checks for terminal state. - */ -bool isTerminalState(IR::ID id) { - return (id.name == IR::ParserState::reject || id.name == IR::ParserState::accept); -} - -bool AnalyzeParser::preorder(const IR::ParserState* state) { - LOG1("Found state " << dbp(state) << " of " << current->parser->name); - if (state->name.name == IR::ParserState::start) - current->start = state; - current->addState(state); - currentState = state; - return true; -} - -void AnalyzeParser::postorder(const IR::ParserState*) { - currentState = nullptr; -} - -void AnalyzeParser::postorder(const IR::ArrayIndex* array) { - // ignore arrays with concrete arguments - if (array->right->is()) - return; - // tries to collect the name of a header stack for current state of a parser. - current->addStateHSUsage(currentState, array->left); -} - -void AnalyzeParser::postorder(const IR::Member* member) { - // tries to collect the name of a header stack for current state of a parser. - current->addStateHSUsage(currentState, member->expr); -} - -void AnalyzeParser::postorder(const IR::PathExpression* expression) { - auto state = findContext(); - if (state == nullptr) - return; - auto decl = refMap->getDeclaration(expression->path); - if (decl->is()) - current->calls(state, decl->to()); -} - -namespace ParserStructureImpl { - -/// Visited map of pairs : -/// 1) name of the parser state and values of the header stack indexes. -/// 2) value of index which is used for generation of the new names of the parsers' states. -using StatesVisitedMap = std::map; - -// Makes transformation of the statements of a parser state. -// It updates indexes of a header stack and generates correct name of the next transition. -class ParserStateRewriter : public Transform { - public: - /// Default constructor. - ParserStateRewriter(ParserStructure* parserStructure, ParserStateInfo* state, - ValueMap* valueMap, ReferenceMap* refMap, TypeMap* typeMap, - ExpressionEvaluator* afterExec, StatesVisitedMap& visitedStates) : - parserStructure(parserStructure), state(state), - valueMap(valueMap), refMap(refMap), typeMap(typeMap), afterExec(afterExec), - visitedStates(visitedStates), wasOutOfBound(false) { - CHECK_NULL(parserStructure); CHECK_NULL(state); - CHECK_NULL(refMap); CHECK_NULL(typeMap); - CHECK_NULL(parserStructure); CHECK_NULL(state); - currentIndex = 0; - } - - /// Updates indexes of a header stack. - IR::Node* preorder(IR::ArrayIndex* expression) { - ParserStateRewriter rewriter(parserStructure, state, valueMap, refMap, typeMap, - afterExec, visitedStates); - auto basetype = getTypeArray(expression->left); - if (!basetype->is()) - return expression; - IR::ArrayIndex* newExpression = expression->clone(); - ExpressionEvaluator ev(refMap, typeMap, valueMap); - auto* value = ev.evaluate(expression->right, false); - if (!value->is()) - return expression; - auto* res = value->to()->constant->clone(); - newExpression->right = res; - if (!res->fitsInt64()) { - // we need to leave expression as is. - ::warning(ErrorType::ERR_EXPRESSION, "Index can't be concretized : %1%", - expression); - return expression; - } - const auto* arrayType = basetype->to(); - if (res->asUnsigned() >= arrayType->getSize()) { - wasOutOfBound = true; - return expression; - } - return newExpression; - } - - /// Eliminates header stack acces next, last operations. - IR::Node* postorder(IR::Member* expression) { - if (!afterExec) - return expression; - auto basetype = getTypeArray(getOriginal()->expr); - if (basetype->is()) { - auto l = afterExec->get(expression->expr); - BUG_CHECK(l->is(), "%1%: expected an array", l); - auto array = l->to(); - unsigned idx = 0; - unsigned offset = 0; - if (state->statesIndexes.count(expression->expr)) { - idx = state->statesIndexes.at(expression->expr); - if (expression->member.name != IR::Type_Stack::last) { - offset = 1; - } - } - if (expression->member.name == IR::Type_Stack::lastIndex) { - return new IR::Constant(IR::Type_Bits::get(32), idx); - } else { - if (idx + offset >= array->size) { - wasOutOfBound = true; - return expression; - } - state->statesIndexes[expression->expr] = idx + offset; - return new IR::ArrayIndex(expression->expr->clone(), - new IR::Constant(IR::Type_Bits::get(32), idx + offset)); - } - } - return expression; - } - - /// Adds a new index for transition if it is required by algorithm. - IR::Node* postorder(IR::PathExpression* expression) { - if (!expression->type->is()) - return expression; - IR::ID newName = genNewName(expression->path->name); - if (newName.name == expression->path->name.name) // first call - return expression; - // need to change name - return new IR::PathExpression(expression->type, new IR::Path(newName, false)); - } - inline size_t getIndex() { return currentIndex; } - bool isOutOfBound() {return wasOutOfBound;} - - protected: - const IR::Type* getTypeArray(const IR::Node* element) { - if (element->is()) { - const IR::Expression* left = element->to()->left; - if (left->type->is()) - return left->type->to()->elementType; - } - return typeMap->getType(element, true); - } - - /// Checks if this state was called previously with the same state of header stack indexes. - /// If it was called then it returns true and generates a new name with the stored index. - bool was_called(cstring nm, IR::ID& id) { - if (state->scenarioStates.count(id.name) && state->statesIndexes.size() == 0) - return false; - auto i = visitedStates.find(VisitedKey(nm, state->statesIndexes)); - if (i == visitedStates.end()) - return false; - if (i->second > 0) - id = IR::ID(id.name + std::to_string(i->second)); - currentIndex = i->second; - return true; - } - - /// Returns true for current id if indexes of the headers stack - /// are the same before previous call of the same parser state. - bool calledWithNoChanges(IR::ID id, const ParserStateInfo* state) { - CHECK_NULL(state); - const auto* prevState = state; - while (prevState->state->name.name != id.name) { - prevState = prevState->predecessor; - if (prevState == nullptr) { - return false; - } - } - if (prevState->predecessor != nullptr) { - prevState = prevState->predecessor; - } else if (prevState == state) { - // The map should be empty if no next operators in a start. - return state->statesIndexes.empty(); - } - return prevState->statesIndexes == state->statesIndexes; - } - - /// Generated new state name - IR::ID genNewName(IR::ID id) { - if (isTerminalState(id)) - return id; - size_t index = 0; - cstring name = id.name; - if (parserStructure->callsIndexes.count(id.name) && (state->scenarioStates.count(id.name) - || parserStructure->reachableHSUsage(id, state))) { - // or self called with no extraction... - // in this case we need to use the same name as it was in previous call - if (was_called(name, id)) - return id; - if (calledWithNoChanges(id, state)) { - index = 0; - if (parserStructure->callsIndexes.count(id.name)) { - index = parserStructure->callsIndexes[id.name]; - } - if (index > 0) { - id = IR::ID(id.name + std::to_string(index)); - } - } else { - index = parserStructure->callsIndexes[id.name]; - parserStructure->callsIndexes[id.name] = index + 1; - if (index + 1 > 0) { - index++; - id = IR::ID(id.name + std::to_string(index)); - } - } - } else if (!parserStructure->callsIndexes.count(id.name)) { - index = 0; - parserStructure->callsIndexes[id.name] = 0; - } - currentIndex = index; - visitedStates.emplace(VisitedKey(name, state->statesIndexes), index); - return id; - } - - private: - ParserStructure* parserStructure; - ParserStateInfo* state; - ValueMap* valueMap; - ReferenceMap* refMap; - TypeMap* typeMap; - ExpressionEvaluator* afterExec; - StatesVisitedMap& visitedStates; - size_t currentIndex; - bool wasOutOfBound; -}; - -class ParserSymbolicInterpreter { - friend class ParserStateRewriter; - - protected: - ParserStructure* structure; - const IR::P4Parser* parser; - ReferenceMap* refMap; - TypeMap* typeMap; - SymbolicValueFactory* factory; - ParserInfo* synthesizedParser; // output produced - bool unroll; - StatesVisitedMap visitedStates; - bool& wasError; - - ValueMap* initializeVariables() { - wasError = false; - ValueMap* result = new ValueMap(); - ExpressionEvaluator ev(refMap, typeMap, result); - - for (auto p : parser->getApplyParameters()->parameters) { - auto type = typeMap->getType(p); - bool initialized = p->direction == IR::Direction::In || - p->direction == IR::Direction::InOut; - auto value = factory->create(type, !initialized); - result->set(p, value); - } - for (auto d : parser->parserLocals) { - auto type = typeMap->getType(d); - SymbolicValue* value = nullptr; - if (d->is()) { - auto dc = d->to(); - value = ev.evaluate(dc->initializer, false); - } else if (d->is()) { - auto dv = d->to(); - if (dv->initializer != nullptr) - value = ev.evaluate(dv->initializer, false); - } else if (d->is()) { - // The midend symbolic interpreter does not have - // a representation for value_set. - continue; - } - - if (value == nullptr) - value = factory->create(type, true); - if (value->is()) { - ::warning(ErrorType::ERR_EXPRESSION, - "%1%: %2%", d, value->to()->message()); - return nullptr; - } - if (value != nullptr) - result->set(d, value); - } - return result; - } - - ParserStateInfo* newStateInfo(const ParserStateInfo* predecessor, - cstring stateName, ValueMap* values, size_t index) { - if (stateName == IR::ParserState::accept || - stateName == IR::ParserState::reject) - return nullptr; - auto state = structure->get(stateName); - auto pi = new ParserStateInfo(stateName, parser, state, predecessor, values->clone(), - index); - synthesizedParser->add(pi); - return pi; - } - - static void stateChain(const ParserStateInfo* state, std::stringstream& stream) { - if (state->predecessor != nullptr) { - stateChain(state->predecessor, stream); - stream << ", "; - } - stream << state->state->externalName(); - } - - static cstring stateChain(const ParserStateInfo* state) { - CHECK_NULL(state); - std::stringstream result; - result << "Parser " << state->parser->externalName() << " state chain: "; - stateChain(state, result); - return result.str(); - } - - /// Return false if an error can be detected statically - bool reportIfError(const ParserStateInfo* state, SymbolicValue* value) const { - if (value->is()) { - auto exc = value->to(); - - bool stateClone = false; - auto orig = state->state; - auto crt = state; - while (crt->predecessor != nullptr) { - crt = crt->predecessor; - if (crt->state == orig) { - stateClone = true; - break; - } - } - - if (!stateClone) - // errors in the original state are signalled - ::warning(ErrorType::ERR_EXPRESSION, "%1%: error %2% will be triggered\n%3%", - exc->errorPosition, exc->message(), stateChain(state)); - // else this error will occur in a clone of the state produced - // by unrolling - if the state is reached. So we don't give an error. - return false; - } - if (!value->is()) - return true; - return false; - } - - /// Executes symbolically the specified statement. - /// Returns pointer to generated statement if execution completes successfully, - /// and 'nullptr' if an error occurred. - const IR::StatOrDecl* executeStatement(ParserStateInfo* state, const IR::StatOrDecl* sord, - ValueMap* valueMap) { - const IR::StatOrDecl* newSord = nullptr; - ExpressionEvaluator ev(refMap, typeMap, valueMap); - - SymbolicValue* errorValue = nullptr; - bool success = true; - if (sord->is()) { - auto ass = sord->to(); - auto left = ev.evaluate(ass->left, true); - errorValue = left; - success = reportIfError(state, left); - if (success) { - auto right = ev.evaluate(ass->right, false); - errorValue = right; - success = reportIfError(state, right); - if (success) - left->assign(right); - } - } else if (sord->is()) { - // can have side-effects - auto mc = sord->to(); - auto e = ev.evaluate(mc->methodCall, false); - errorValue = e; - success = reportIfError(state, e); - } else if (auto bs = sord->to()) { - IR::IndexedVector newComponents; - for (auto* component : bs->components) { - auto newComponent = executeStatement(state, component, valueMap); - if (!newComponent) - success = false; - else - newComponents.push_back(newComponent); - } - sord = new IR::BlockStatement(newComponents); - } else { - BUG("%1%: unexpected declaration or statement", sord); - } - if (!success) { - if (errorValue->is()) { - auto* exc = errorValue->to(); - if (exc->exc == P4::StandardExceptions::StackOutOfBounds) { - return newSord; - } - } - std::stringstream errorStr; - errorStr << errorValue; - ::warning(ErrorType::WARN_IGNORE_PROPERTY, - "Result of %1% is not defined: %2%", sord, errorStr.str()); - } - ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, &ev, - visitedStates); - const IR::Node* node = sord->apply(rewriter); - if (rewriter.isOutOfBound()) { - return nullptr; - } - newSord = node->to(); - LOG2("After " << sord << " state is\n" << valueMap); - return newSord; - } - - using EvaluationSelectResult = std::pair*, - const IR::Expression*>; - - EvaluationSelectResult evaluateSelect(ParserStateInfo* state, - ValueMap* valueMap) { - const IR::Expression* newSelect = nullptr; - auto select = state->state->selectExpression; - if (select == nullptr) - return EvaluationSelectResult(nullptr, nullptr); - - auto result = new std::vector(); - if (select->is()) { - auto path = select->to()->path; - auto next = refMap->getDeclaration(path); - BUG_CHECK(next->is(), "%1%: expected a state", path); - // update call indexes - ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, nullptr, - visitedStates); - const IR::Expression* node = select->apply(rewriter); - if (rewriter.isOutOfBound()) { - return EvaluationSelectResult(nullptr, nullptr); - } - CHECK_NULL(node); - newSelect = node->to(); - CHECK_NULL(newSelect); - auto nextInfo = newStateInfo(state, next->getName(), state->after, rewriter.getIndex()); - if (nextInfo != nullptr) { - nextInfo->scenarioStates = state->scenarioStates; - result->push_back(nextInfo); - } - } else if (select->is()) { - // TODO: really try to match cases; today we are conservative - auto se = select->to(); - IR::Vector newSelectCases; - ExpressionEvaluator ev(refMap, typeMap, valueMap); - try { - ev.evaluate(se->select, true); - } - catch (...) { - // Ignore throws from evaluator. - // If an index of a header stack is not substituted then - // we should leave a state as is. - } - ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, &ev, - visitedStates); - const IR::Node* node = se->select->apply(rewriter); - if (rewriter.isOutOfBound()) { - return EvaluationSelectResult(nullptr, nullptr); - } - const IR::ListExpression* newListSelect = node->to(); - auto etalonStateIndexes = state->statesIndexes; - for (auto c : se->selectCases) { - auto currentStateIndexes = etalonStateIndexes; - auto path = c->state->path; - auto next = refMap->getDeclaration(path); - BUG_CHECK(next->is(), "%1%: expected a state", path); - - // update call indexes - ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, - nullptr, visitedStates); - const IR::Node* node = c->apply(rewriter); - if (rewriter.isOutOfBound()) { - return EvaluationSelectResult(nullptr, nullptr); - } - CHECK_NULL(node); - auto newC = node->to(); - CHECK_NULL(newC); - newSelectCases.push_back(newC); - - auto nextInfo = newStateInfo(state, next->getName(), state->after, - rewriter.getIndex()); - if (nextInfo != nullptr) { - nextInfo->scenarioStates = state->scenarioStates; - nextInfo->statesIndexes = currentStateIndexes; - result->push_back(nextInfo); - } - } - newSelect = new IR::SelectExpression(newListSelect, newSelectCases); - } else { - BUG("%1%: unexpected expression", select); - } - return EvaluationSelectResult(result, newSelect); - } - - static bool headerValidityChanged(const SymbolicValue* first, const SymbolicValue* second) { - CHECK_NULL(first); CHECK_NULL(second); - if (first->is()) { - auto fhdr = first->to(); - auto shdr = second->to(); - CHECK_NULL(shdr); - return !fhdr->valid->equals(shdr->valid); - } else if (first->is()) { - auto farray = first->to(); - auto sarray = second->to(); - CHECK_NULL(sarray); - for (size_t i = 0; i < farray->size; i++) { - auto hdr = farray->get(nullptr, i); - auto newHdr = sarray->get(nullptr, i); - if (headerValidityChanged(hdr, newHdr)) - return true; - } - } else if (first->is()) { - auto fstruct = first->to(); - auto sstruct = second->to(); - CHECK_NULL(sstruct); - for (auto f : fstruct->fieldValue) { - auto ffield = fstruct->get(nullptr, f.first); - auto sfield = sstruct->get(nullptr, f.first); - if (headerValidityChanged(ffield, sfield)) - return true; - } - } - return false; - } - - /// True if any header has changed its "validity" bit - static bool headerValidityChange(const ValueMap* before, const ValueMap* after) { - for (auto v : before->map) { - auto value = v.second; - if (headerValidityChanged(value, after->get(v.first))) - return true; - } - return false; - } - - /// True if both structures are equal. - bool equStackVariableMap(const StackVariableMap& l, const StackVariableMap& r) const { - if (l.empty()) { - return r.empty(); - } - for (const auto& i : l) { - const auto j = r.find(i.first); - if (j == r.end() || i.second != j->second) { - return false; - } - } - return true; - } - - /// Return true if we have detected a loop we cannot unroll - bool checkLoops(ParserStateInfo* state) const { - const ParserStateInfo* crt = state; - while (true) { - crt = crt->predecessor; - if (crt == nullptr) - break; - if (crt->state == state->state) { - // Loop detected. - // Check if any packet in the valueMap has changed - auto filter = [](const IR::IDeclaration*, const SymbolicValue* value) - { return value->is(); }; - auto packets = state->before->filter(filter); - auto prevPackets = crt->before->filter(filter); - if (packets->equals(prevPackets)) { - for (auto p : state->before->map) { - if (p.second->is()) { - auto pkt = p.second->to(); - if (pkt->isConservative()) { - break; - } - } - } - if (equStackVariableMap(crt->statesIndexes, state->statesIndexes)) { - ::warning(ErrorType::ERR_INVALID, - "Parser cycle can't be unrolled, because ParserUnroll can't " - "detect the number of loop iterations:\n%1%", - stateChain(state)); - wasError = true; - } - return true; - } - - // If no header validity has changed we can't really unroll - if (!headerValidityChange(crt->before, state->before)) { - if (equStackVariableMap(crt->statesIndexes, state->statesIndexes)) { - ::warning(ErrorType::ERR_INVALID, - "Parser cycle can't be unrolled, because ParserUnroll can't " - "detect the number of loop iterations:\n%1%", - stateChain(state)); - wasError = true; - } - return true; - } - break; - } - } - return false; - } - - /// Gets new name for a state - IR::ID getNewName(ParserStateInfo* state) { - if (state->currentIndex == 0) { - return state->state->name; - } - return IR::ID(state->state->name + std::to_string(state->currentIndex)); - } - - using EvaluationStateResult = std::pair*, bool>; - - /// Generates new state with the help of symbolic execution. - /// If corresponded state was generated previously then it returns @a nullptr and false. - /// @param newStates is a set of parsers' names which were genereted. - EvaluationStateResult evaluateState(ParserStateInfo* state, - std::unordered_set &newStates) { - LOG1("Analyzing " << dbp(state->state)); - auto valueMap = state->before->clone(); - IR::IndexedVector components; - IR::ID newName; - if (unroll) { - newName = getNewName(state); - if (newStates.count(newName)) - return EvaluationStateResult(nullptr, false); - newStates.insert(newName); - } - for (auto s : state->state->components) { - auto* newComponent = executeStatement(state, s, valueMap); - if (!newComponent) - return EvaluationStateResult(nullptr, true); - if (unroll) - components.push_back(newComponent); - } - state->after = valueMap; - auto result = evaluateSelect(state, valueMap); - if (unroll) { - if (result.second == nullptr) { - return EvaluationStateResult(nullptr, true); - } - if (state->name == newName) { - state->newState = new IR::ParserState(state->state->srcInfo, newName, - state->state->annotations, components, - result.second); - } else { - state->newState = - new IR::ParserState(state->state->srcInfo, newName, components, result.second); - } - } - return EvaluationStateResult(result.first, true); - } - - public: - bool hasOutOfboundState; - /// constructor - ParserSymbolicInterpreter(ParserStructure* structure, ReferenceMap* refMap, TypeMap* typeMap, - bool unroll, bool& wasError) : structure(structure), refMap(refMap), - typeMap(typeMap), synthesizedParser(nullptr), unroll(unroll), - wasError(wasError) { - CHECK_NULL(structure); CHECK_NULL(refMap); CHECK_NULL(typeMap); - factory = new SymbolicValueFactory(typeMap); - parser = structure->parser; - hasOutOfboundState = false; - } - - /// generate call OutOfBound - void addOutFoBound(ParserStateInfo* stateInfo, std::unordered_set& newStates, - bool checkBefore = true) { - IR::ID newName = getNewName(stateInfo); - if (checkBefore && newStates.count(newName)) { - return; - } - hasOutOfboundState = true; - newStates.insert(newName); - stateInfo->newState = new IR::ParserState(newName, - IR::IndexedVector(), - new IR::PathExpression(new IR::Type_State(), - new IR::Path(outOfBoundsStateName, false))); - } - - /// running symbolic execution - ParserInfo* run() { - synthesizedParser = new ParserInfo(); - auto initMap = initializeVariables(); - if (initMap == nullptr) - // error during initializer evaluation - return synthesizedParser; - auto startInfo = newStateInfo(nullptr, structure->start->name.name, initMap, 0); - structure->callsIndexes.emplace(structure->start->name.name, 0); - startInfo->scenarioStates.insert(structure->start->name.name); - std::vector toRun; // worklist - toRun.push_back(startInfo); - std::set visited; - std::unordered_set newStates; - while (!toRun.empty()) { - auto stateInfo = toRun.back(); - toRun.pop_back(); - LOG1("Symbolic evaluation of " << stateChain(stateInfo)); - // checking visited state, loop state, and the reachable states with needed header stack - // operators. - if (visited.count(VisitedKey(stateInfo)) && - !stateInfo->scenarioStates.count(stateInfo->name) && - !structure->reachableHSUsage(stateInfo->state->name, stateInfo)) - continue; - auto iHSNames = structure->statesWithHeaderStacks.find(stateInfo->name); - if (iHSNames != structure->statesWithHeaderStacks.end()) - stateInfo->scenarioHS.insert(iHSNames->second.begin(), iHSNames->second.end()); - visited.insert(VisitedKey(stateInfo)); // add to visited map - stateInfo->scenarioStates.insert(stateInfo->name); // add to loops detection - bool infLoop = checkLoops(stateInfo); - if (infLoop) { - // Stop unrolling if it was an error. - if (wasError) { - IR::ID newName = getNewName(stateInfo); - if (newStates.count(newName) != 0) { - evaluateState(stateInfo, newStates); - } - wasError = false; - continue; - } - // don't evaluate successors anymore - // generate call OutOfBound - addOutFoBound(stateInfo, newStates); - continue; - } - IR::ID newName = getNewName(stateInfo); - bool notAdded = newStates.count(newName) == 0; - auto nextStates = evaluateState(stateInfo, newStates); - if (nextStates.first == nullptr) { - if (nextStates.second && stateInfo->predecessor && - newName.name !=stateInfo->predecessor->newState->name) { - // generate call OutOfBound - addOutFoBound(stateInfo, newStates, false); - } else { - // save current state - if (notAdded) { - stateInfo->newState = stateInfo->state->clone(); - } - } - LOG1("No next states"); - continue; - } - toRun.insert(toRun.end(), nextStates.first->begin(), nextStates.first->end()); - } - - return synthesizedParser; - } -}; - -} // namespace ParserStructureImpl - -bool ParserStructure::analyze(ReferenceMap* refMap, TypeMap* typeMap, bool unroll, bool& wasError) { - ParserStructureImpl::ParserSymbolicInterpreter psi(this, refMap, typeMap, unroll, wasError); - result = psi.run(); - return psi.hasOutOfboundState; -} - -/// check reachability for usage of header stack -bool ParserStructure::reachableHSUsage(IR::ID id, const ParserStateInfo* state) const { - if (!state->scenarioHS.size()) - return false; - CHECK_NULL(callGraph); - const IR::IDeclaration* declaration = parser->states.getDeclaration(id.name); - BUG_CHECK(declaration && declaration->is(), "Invalid declaration %1%", id); - std::set reachableStates; - callGraph->reachable(declaration->to(), reachableStates); - std::set reachebleHSoperators; - for (auto i : reachableStates) { - auto iHSNames = statesWithHeaderStacks.find(i->name); - if (iHSNames != statesWithHeaderStacks.end()) - reachebleHSoperators.insert(iHSNames->second.begin(), iHSNames->second.end()); - } - std::set intersectionHSOperators; - std::set_intersection(state->scenarioHS.begin(), state->scenarioHS.end(), - reachebleHSoperators.begin(), reachebleHSoperators.end(), - std::inserter(intersectionHSOperators, - intersectionHSOperators.begin())); - return intersectionHSOperators.size() > 0; -} - -void ParserStructure::addStateHSUsage(const IR::ParserState* state, - const IR::Expression* expression) { - if (state == nullptr || expression == nullptr || !expression->type->is()) - return; - auto i = statesWithHeaderStacks.find(state->name.name); - if (i == statesWithHeaderStacks.end()) { - std::set s; - s.insert(expression->toString()); - statesWithHeaderStacks.emplace(state->name.name, s); - } else { - i->second.insert(expression->toString()); - } -} - - -} // namespace P4 +#include "parserUnroll.h" +#include "interpreter.h" +#include "lib/hash.h" +#include "lib/stringify.h" +#include "ir/ir.h" + +namespace P4 { + +StackVariable::StackVariable(const IR::Expression* expr) : variable(expr) { + CHECK_NULL(expr); + BUG_CHECK(repOk(expr), "Invalid stack variable %1%", expr); + variable = expr; +} + +bool StackVariable::repOk(const IR::Expression* expr) { + // Only members and path expression can be stack variables. + const auto* member = expr->to(); + if (member == nullptr) { + return expr->is(); + } + + // A member is a stack variable if it is qualified by a PathExpression or if its qualifier is a + // stack variable. + return member->expr->is() || repOk(member->expr); +} + +bool StackVariable::operator==(const StackVariable& other) const { + // Delegate to IR's notion of equality. + return variable->equiv(*other.variable); +} + +size_t StackVariableHash::operator()(const StackVariable& var) const { + // hash for path expression. + if (const auto* path = var.variable->to()) { + return Util::Hash::fnv1a(path->path->name.name); + } + const IR::Member* curMember = var.variable->to(); + std::vector h; + while (curMember) { + h.push_back(Util::Hash::fnv1a(curMember->member.name)); + if (auto* path = curMember->expr->to()) { + h.push_back(Util::Hash::fnv1a(path->path->name)); + break; + } + curMember = curMember->expr->checkedTo(); + } + return Util::Hash::fnv1a(h.data(), sizeof(size_t) * h.size()); +} + +/// The main class for parsers' states key for visited checking. +struct VisitedKey { + cstring name; // name of a state. + StackVariableMap indexes; // indexes of header stacks. + + VisitedKey(cstring name, StackVariableMap& indexes) + : name(name), indexes(indexes) {} + + explicit VisitedKey(const ParserStateInfo* stateInfo) { + CHECK_NULL(stateInfo); + name = stateInfo->state->name.name; + indexes = stateInfo->statesIndexes; + } + + /// Checks of two states to be less. If @a name < @a e.name then it returns @a true. + /// if @a name > @a e.name then it returns false. + /// If @a name is equal @a e.name then it checks the values of the headers stack indexes. + /// For both map of header stacks' indexes it creates map of pairs of values for each + /// header stack index. + /// If some indexes are missing then it cosiders them as -1. + /// Next, it continues checking each pair in a intermidiate map with the same approach + /// as for @a name and @a e.name. + bool operator<(const VisitedKey& e) const { + if (name < e.name) + return true; + if (name > e.name) + return false; + std::unordered_map, StackVariableHash > mp; + for (auto& i1 : indexes) { + mp.emplace(i1.first, std::make_pair(i1.second, -1)); + } + for (auto& i2 : e.indexes) { + auto ref = mp.find(i2.first); + if (ref == mp.end()) + mp.emplace(i2.first, std::make_pair(-1, i2.second)); + else + ref->second.second = i2.second; + } + for (auto j : mp) { + if (j.second.first < j.second.second) + return true; + if (j.second.first > j.second.second) + return false; + } + return false; + } +}; + +/** + * Checks for terminal state. + */ +bool isTerminalState(IR::ID id) { + return (id.name == IR::ParserState::reject || id.name == IR::ParserState::accept); +} + +bool AnalyzeParser::preorder(const IR::ParserState* state) { + LOG1("Found state " << dbp(state) << " of " << current->parser->name); + if (state->name.name == IR::ParserState::start) + current->start = state; + current->addState(state); + currentState = state; + return true; +} + +void AnalyzeParser::postorder(const IR::ParserState*) { + currentState = nullptr; +} + +void AnalyzeParser::postorder(const IR::ArrayIndex* array) { + // ignore arrays with concrete arguments + if (array->right->is()) + return; + // tries to collect the name of a header stack for current state of a parser. + current->addStateHSUsage(currentState, array->left); +} + +void AnalyzeParser::postorder(const IR::Member* member) { + // tries to collect the name of a header stack for current state of a parser. + current->addStateHSUsage(currentState, member->expr); +} + +void AnalyzeParser::postorder(const IR::PathExpression* expression) { + auto state = findContext(); + if (state == nullptr) + return; + auto decl = refMap->getDeclaration(expression->path); + if (decl->is()) + current->calls(state, decl->to()); +} + +namespace ParserStructureImpl { + +/// Visited map of pairs : +/// 1) name of the parser state and values of the header stack indexes. +/// 2) value of index which is used for generation of the new names of the parsers' states. +using StatesVisitedMap = std::map; + +// Makes transformation of the statements of a parser state. +// It updates indexes of a header stack and generates correct name of the next transition. +class ParserStateRewriter : public Transform { + public: + /// Default constructor. + ParserStateRewriter(ParserStructure* parserStructure, ParserStateInfo* state, + ValueMap* valueMap, ReferenceMap* refMap, TypeMap* typeMap, + ExpressionEvaluator* afterExec, StatesVisitedMap& visitedStates) : + parserStructure(parserStructure), state(state), + valueMap(valueMap), refMap(refMap), typeMap(typeMap), afterExec(afterExec), + visitedStates(visitedStates), wasOutOfBound(false) { + CHECK_NULL(parserStructure); CHECK_NULL(state); + CHECK_NULL(refMap); CHECK_NULL(typeMap); + CHECK_NULL(parserStructure); CHECK_NULL(state); + currentIndex = 0; + } + + /// Updates indexes of a header stack. + IR::Node* preorder(IR::ArrayIndex* expression) { + ParserStateRewriter rewriter(parserStructure, state, valueMap, refMap, typeMap, + afterExec, visitedStates); + auto basetype = getTypeArray(expression->left); + if (!basetype->is()) + return expression; + IR::ArrayIndex* newExpression = expression->clone(); + ExpressionEvaluator ev(refMap, typeMap, valueMap); + auto* value = ev.evaluate(expression->right, false); + if (!value->is()) + return expression; + auto* res = value->to()->constant->clone(); + newExpression->right = res; + if (!res->fitsInt64()) { + // we need to leave expression as is. + ::warning(ErrorType::ERR_EXPRESSION, "Index can't be concretized : %1%", + expression); + return expression; + } + const auto* arrayType = basetype->to(); + if (res->asUnsigned() >= arrayType->getSize()) { + wasOutOfBound = true; + return expression; + } + state->substitutedIndexes[newExpression->left] = res; + return newExpression; + } + + /// Eliminates header stack acces next, last operations. + IR::Node* postorder(IR::Member* expression) { + if (!afterExec) + return expression; + auto basetype = getTypeArray(getOriginal()->expr); + if (basetype->is()) { + auto l = afterExec->get(expression->expr); + BUG_CHECK(l->is(), "%1%: expected an array", l); + auto array = l->to(); + unsigned idx = 0; + unsigned offset = 0; + if (state->statesIndexes.count(expression->expr)) { + idx = state->statesIndexes.at(expression->expr); + if (expression->member.name != IR::Type_Stack::last) { + offset = 1; + } + } + if (expression->member.name == IR::Type_Stack::lastIndex) { + return new IR::Constant(IR::Type_Bits::get(32), idx); + } else { + if (idx + offset >= array->size) { + wasOutOfBound = true; + return expression; + } + state->statesIndexes[expression->expr] = idx + offset; + return new IR::ArrayIndex(expression->expr->clone(), + new IR::Constant(IR::Type_Bits::get(32), idx + offset)); + } + } + return expression; + } + + /// Adds a new index for transition if it is required by algorithm. + IR::Node* postorder(IR::PathExpression* expression) { + if (!expression->type->is()) + return expression; + IR::ID newName = genNewName(expression->path->name); + if (newName.name == expression->path->name.name) // first call + return expression; + // need to change name + return new IR::PathExpression(expression->type, new IR::Path(newName, false)); + } + inline size_t getIndex() { return currentIndex; } + bool isOutOfBound() {return wasOutOfBound;} + + protected: + const IR::Type* getTypeArray(const IR::Node* element) { + if (element->is()) { + const IR::Expression* left = element->to()->left; + if (left->type->is()) + return left->type->to()->elementType; + } + return typeMap->getType(element, true); + } + + /// Checks if this state was called previously with the same state of header stack indexes. + /// If it was called then it returns true and generates a new name with the stored index. + bool was_called(cstring nm, IR::ID& id) { + if (state->scenarioStates.count(id.name) && state->statesIndexes.size() == 0) + return false; + auto i = visitedStates.find(VisitedKey(nm, state->statesIndexes)); + if (i == visitedStates.end()) + return false; + if (i->second > 0) + id = IR::ID(id.name + std::to_string(i->second)); + currentIndex = i->second; + return true; + } + + /// Checks values of the headers stacks which were evaluated. + bool checkIndexes(const StackVariableIndexMap& prev, const StackVariableIndexMap& cur) { + if (prev.size() != cur.size()) { + return false; + } + for (auto& i : prev) { + auto j = cur.find(i.first); + if (j == cur.end()) { + return false; + } + if (!i.second->equiv(*j->second)) { + return false; + } + } + return true; + } + + /// Returns true for current id if indexes of the headers stack + /// are the same before previous call of the same parser state. + bool calledWithNoChanges(IR::ID id, const ParserStateInfo* state) { + CHECK_NULL(state); + const auto* prevState = state; + while (prevState->state->name.name != id.name) { + prevState = prevState->predecessor; + if (prevState == nullptr) { + return false; + } + } + if (prevState->predecessor != nullptr) { + prevState = prevState->predecessor; + } else if (prevState == state && state->predecessor == nullptr) { + // The map should be empty if no next operators in a start. + return state->statesIndexes.empty(); + } + return prevState->statesIndexes == state->statesIndexes && + checkIndexes(prevState->substitutedIndexes, state->substitutedIndexes); + } + + /// Generated new state name + IR::ID genNewName(IR::ID id) { + if (isTerminalState(id)) + return id; + size_t index = 0; + cstring name = id.name; + if (parserStructure->callsIndexes.count(id.name) && (state->scenarioStates.count(id.name) + || parserStructure->reachableHSUsage(id, state))) { + // or self called with no extraction... + // in this case we need to use the same name as it was in previous call + if (was_called(name, id)) + return id; + if (calledWithNoChanges(id, state)) { + index = 0; + if (parserStructure->callsIndexes.count(id.name)) { + index = parserStructure->callsIndexes[id.name]; + } + if (index > 0) { + id = IR::ID(id.name + std::to_string(index)); + } + } else { + index = parserStructure->callsIndexes[id.name]; + parserStructure->callsIndexes[id.name] = index + 1; + if (index + 1 > 0) { + index++; + id = IR::ID(id.name + std::to_string(index)); + } + } + } else if (!parserStructure->callsIndexes.count(id.name)) { + index = 0; + parserStructure->callsIndexes[id.name] = 0; + } + currentIndex = index; + visitedStates.emplace(VisitedKey(name, state->statesIndexes), index); + return id; + } + + private: + ParserStructure* parserStructure; + ParserStateInfo* state; + ValueMap* valueMap; + ReferenceMap* refMap; + TypeMap* typeMap; + ExpressionEvaluator* afterExec; + StatesVisitedMap& visitedStates; + size_t currentIndex; + bool wasOutOfBound; +}; + +class ParserSymbolicInterpreter { + friend class ParserStateRewriter; + + protected: + ParserStructure* structure; + const IR::P4Parser* parser; + ReferenceMap* refMap; + TypeMap* typeMap; + SymbolicValueFactory* factory; + ParserInfo* synthesizedParser; // output produced + bool unroll; + StatesVisitedMap visitedStates; + bool& wasError; + + ValueMap* initializeVariables() { + wasError = false; + ValueMap* result = new ValueMap(); + ExpressionEvaluator ev(refMap, typeMap, result); + + for (auto p : parser->getApplyParameters()->parameters) { + auto type = typeMap->getType(p); + bool initialized = p->direction == IR::Direction::In || + p->direction == IR::Direction::InOut; + auto value = factory->create(type, !initialized); + result->set(p, value); + } + for (auto d : parser->parserLocals) { + auto type = typeMap->getType(d); + SymbolicValue* value = nullptr; + if (d->is()) { + auto dc = d->to(); + value = ev.evaluate(dc->initializer, false); + } else if (d->is()) { + auto dv = d->to(); + if (dv->initializer != nullptr) + value = ev.evaluate(dv->initializer, false); + } else if (d->is()) { + // The midend symbolic interpreter does not have + // a representation for value_set. + continue; + } + + if (value == nullptr) + value = factory->create(type, true); + if (value->is()) { + ::warning(ErrorType::ERR_EXPRESSION, + "%1%: %2%", d, value->to()->message()); + return nullptr; + } + if (value != nullptr) + result->set(d, value); + } + return result; + } + + ParserStateInfo* newStateInfo(const ParserStateInfo* predecessor, + cstring stateName, ValueMap* values, size_t index) { + if (stateName == IR::ParserState::accept || + stateName == IR::ParserState::reject) + return nullptr; + auto state = structure->get(stateName); + auto pi = new ParserStateInfo(stateName, parser, state, predecessor, values->clone(), + index); + synthesizedParser->add(pi); + return pi; + } + + static void stateChain(const ParserStateInfo* state, std::stringstream& stream) { + if (state->predecessor != nullptr) { + stateChain(state->predecessor, stream); + stream << ", "; + } + stream << state->state->externalName(); + } + + static cstring stateChain(const ParserStateInfo* state) { + CHECK_NULL(state); + std::stringstream result; + result << "Parser " << state->parser->externalName() << " state chain: "; + stateChain(state, result); + return result.str(); + } + + /// Return false if an error can be detected statically + bool reportIfError(const ParserStateInfo* state, SymbolicValue* value) const { + if (value->is()) { + auto exc = value->to(); + + bool stateClone = false; + auto orig = state->state; + auto crt = state; + while (crt->predecessor != nullptr) { + crt = crt->predecessor; + if (crt->state == orig) { + stateClone = true; + break; + } + } + + if (!stateClone) + // errors in the original state are signalled + ::warning(ErrorType::ERR_EXPRESSION, "%1%: error %2% will be triggered\n%3%", + exc->errorPosition, exc->message(), stateChain(state)); + // else this error will occur in a clone of the state produced + // by unrolling - if the state is reached. So we don't give an error. + return false; + } + if (!value->is()) + return true; + return false; + } + + /// Executes symbolically the specified statement. + /// Returns pointer to generated statement if execution completes successfully, + /// and 'nullptr' if an error occurred. + const IR::StatOrDecl* executeStatement(ParserStateInfo* state, const IR::StatOrDecl* sord, + ValueMap* valueMap) { + const IR::StatOrDecl* newSord = nullptr; + ExpressionEvaluator ev(refMap, typeMap, valueMap); + + SymbolicValue* errorValue = nullptr; + bool success = true; + if (sord->is()) { + auto ass = sord->to(); + auto left = ev.evaluate(ass->left, true); + errorValue = left; + success = reportIfError(state, left); + if (success) { + auto right = ev.evaluate(ass->right, false); + errorValue = right; + success = reportIfError(state, right); + if (success) + left->assign(right); + } + } else if (sord->is()) { + // can have side-effects + auto mc = sord->to(); + auto e = ev.evaluate(mc->methodCall, false); + errorValue = e; + success = reportIfError(state, e); + } else if (auto bs = sord->to()) { + IR::IndexedVector newComponents; + for (auto* component : bs->components) { + auto newComponent = executeStatement(state, component, valueMap); + if (!newComponent) + success = false; + else + newComponents.push_back(newComponent); + } + sord = new IR::BlockStatement(newComponents); + } else { + BUG("%1%: unexpected declaration or statement", sord); + } + if (!success) { + if (errorValue->is()) { + auto* exc = errorValue->to(); + if (exc->exc == P4::StandardExceptions::StackOutOfBounds) { + return newSord; + } + } + std::stringstream errorStr; + errorStr << errorValue; + ::warning(ErrorType::WARN_IGNORE_PROPERTY, + "Result of %1% is not defined: %2%", sord, errorStr.str()); + } + ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, &ev, + visitedStates); + const IR::Node* node = sord->apply(rewriter); + if (rewriter.isOutOfBound()) { + return nullptr; + } + newSord = node->to(); + LOG2("After " << sord << " state is\n" << valueMap); + return newSord; + } + + using EvaluationSelectResult = std::pair*, + const IR::Expression*>; + + EvaluationSelectResult evaluateSelect(ParserStateInfo* state, + ValueMap* valueMap) { + const IR::Expression* newSelect = nullptr; + auto select = state->state->selectExpression; + if (select == nullptr) + return EvaluationSelectResult(nullptr, nullptr); + + auto result = new std::vector(); + if (select->is()) { + auto path = select->to()->path; + auto next = refMap->getDeclaration(path); + BUG_CHECK(next->is(), "%1%: expected a state", path); + // update call indexes + ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, nullptr, + visitedStates); + const IR::Expression* node = select->apply(rewriter); + if (rewriter.isOutOfBound()) { + return EvaluationSelectResult(nullptr, nullptr); + } + CHECK_NULL(node); + newSelect = node->to(); + CHECK_NULL(newSelect); + auto nextInfo = newStateInfo(state, next->getName(), state->after, rewriter.getIndex()); + if (nextInfo != nullptr) { + nextInfo->scenarioStates = state->scenarioStates; + result->push_back(nextInfo); + } + } else if (select->is()) { + // TODO: really try to match cases; today we are conservative + auto se = select->to(); + IR::Vector newSelectCases; + ExpressionEvaluator ev(refMap, typeMap, valueMap); + try { + ev.evaluate(se->select, true); + } + catch (...) { + // Ignore throws from evaluator. + // If an index of a header stack is not substituted then + // we should leave a state as is. + } + ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, &ev, + visitedStates); + const IR::Node* node = se->select->apply(rewriter); + if (rewriter.isOutOfBound()) { + return EvaluationSelectResult(nullptr, nullptr); + } + const IR::ListExpression* newListSelect = node->to(); + auto etalonStateIndexes = state->statesIndexes; + for (auto c : se->selectCases) { + auto currentStateIndexes = etalonStateIndexes; + auto path = c->state->path; + auto next = refMap->getDeclaration(path); + BUG_CHECK(next->is(), "%1%: expected a state", path); + + // update call indexes + ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, + nullptr, visitedStates); + const IR::Node* node = c->apply(rewriter); + if (rewriter.isOutOfBound()) { + return EvaluationSelectResult(nullptr, nullptr); + } + CHECK_NULL(node); + auto newC = node->to(); + CHECK_NULL(newC); + newSelectCases.push_back(newC); + + auto nextInfo = newStateInfo(state, next->getName(), state->after, + rewriter.getIndex()); + if (nextInfo != nullptr) { + nextInfo->scenarioStates = state->scenarioStates; + nextInfo->statesIndexes = currentStateIndexes; + result->push_back(nextInfo); + } + } + newSelect = new IR::SelectExpression(newListSelect, newSelectCases); + } else { + BUG("%1%: unexpected expression", select); + } + return EvaluationSelectResult(result, newSelect); + } + + static bool headerValidityChanged(const SymbolicValue* first, const SymbolicValue* second) { + CHECK_NULL(first); CHECK_NULL(second); + if (first->is()) { + auto fhdr = first->to(); + auto shdr = second->to(); + CHECK_NULL(shdr); + return !fhdr->valid->equals(shdr->valid); + } else if (first->is()) { + auto farray = first->to(); + auto sarray = second->to(); + CHECK_NULL(sarray); + for (size_t i = 0; i < farray->size; i++) { + auto hdr = farray->get(nullptr, i); + auto newHdr = sarray->get(nullptr, i); + if (headerValidityChanged(hdr, newHdr)) + return true; + } + } else if (first->is()) { + auto fstruct = first->to(); + auto sstruct = second->to(); + CHECK_NULL(sstruct); + for (auto f : fstruct->fieldValue) { + auto ffield = fstruct->get(nullptr, f.first); + auto sfield = sstruct->get(nullptr, f.first); + if (headerValidityChanged(ffield, sfield)) + return true; + } + } + return false; + } + + /// True if any header has changed its "validity" bit + static bool headerValidityChange(const ValueMap* before, const ValueMap* after) { + for (auto v : before->map) { + auto value = v.second; + if (headerValidityChanged(value, after->get(v.first))) + return true; + } + return false; + } + + /// True if both structures are equal. + bool equStackVariableMap(const StackVariableMap& l, const StackVariableMap& r) const { + if (l.empty()) { + return r.empty(); + } + for (const auto& i : l) { + const auto j = r.find(i.first); + if (j == r.end() || i.second != j->second) { + return false; + } + } + return true; + } + + /// Return true if we have detected a loop we cannot unroll + bool checkLoops(ParserStateInfo* state) const { + const ParserStateInfo* crt = state; + while (true) { + crt = crt->predecessor; + if (crt == nullptr) + break; + if (crt->state == state->state) { + // Loop detected. + // Check if any packet in the valueMap has changed + auto filter = [](const IR::IDeclaration*, const SymbolicValue* value) + { return value->is(); }; + auto packets = state->before->filter(filter); + auto prevPackets = crt->before->filter(filter); + if (packets->equals(prevPackets)) { + for (auto p : state->before->map) { + if (p.second->is()) { + auto pkt = p.second->to(); + if (pkt->isConservative()) { + break; + } + } + } + if (equStackVariableMap(crt->statesIndexes, state->statesIndexes)) { + ::warning(ErrorType::ERR_INVALID, + "Parser cycle can't be unrolled, because ParserUnroll can't " + "detect the number of loop iterations:\n%1%", + stateChain(state)); + wasError = true; + } + return true; + } + + // If no header validity has changed we can't really unroll + if (!headerValidityChange(crt->before, state->before)) { + if (equStackVariableMap(crt->statesIndexes, state->statesIndexes)) { + ::warning(ErrorType::ERR_INVALID, + "Parser cycle can't be unrolled, because ParserUnroll can't " + "detect the number of loop iterations:\n%1%", + stateChain(state)); + wasError = true; + } + return true; + } + break; + } + } + return false; + } + + /// Gets new name for a state + IR::ID getNewName(ParserStateInfo* state) { + if (state->currentIndex == 0) { + return state->state->name; + } + return IR::ID(state->state->name + std::to_string(state->currentIndex)); + } + + using EvaluationStateResult = std::pair*, bool>; + + /// Generates new state with the help of symbolic execution. + /// If corresponded state was generated previously then it returns @a nullptr and false. + /// @param newStates is a set of parsers' names which were genereted. + EvaluationStateResult evaluateState(ParserStateInfo* state, + std::unordered_set &newStates) { + LOG1("Analyzing " << dbp(state->state)); + auto valueMap = state->before->clone(); + IR::IndexedVector components; + IR::ID newName; + if (unroll) { + newName = getNewName(state); + if (newStates.count(newName)) + return EvaluationStateResult(nullptr, false); + newStates.insert(newName); + } + for (auto s : state->state->components) { + auto* newComponent = executeStatement(state, s, valueMap); + if (!newComponent) + return EvaluationStateResult(nullptr, true); + if (unroll) + components.push_back(newComponent); + } + state->after = valueMap; + auto result = evaluateSelect(state, valueMap); + if (unroll) { + if (result.second == nullptr) { + return EvaluationStateResult(nullptr, true); + } + if (state->name == newName) { + state->newState = new IR::ParserState(state->state->srcInfo, newName, + state->state->annotations, components, + result.second); + } else { + state->newState = + new IR::ParserState(state->state->srcInfo, newName, components, result.second); + } + } + return EvaluationStateResult(result.first, true); + } + + public: + bool hasOutOfboundState; + /// constructor + ParserSymbolicInterpreter(ParserStructure* structure, ReferenceMap* refMap, TypeMap* typeMap, + bool unroll, bool& wasError) : structure(structure), refMap(refMap), + typeMap(typeMap), synthesizedParser(nullptr), unroll(unroll), + wasError(wasError) { + CHECK_NULL(structure); CHECK_NULL(refMap); CHECK_NULL(typeMap); + factory = new SymbolicValueFactory(typeMap); + parser = structure->parser; + hasOutOfboundState = false; + } + + /// generate call OutOfBound + void addOutFoBound(ParserStateInfo* stateInfo, std::unordered_set& newStates, + bool checkBefore = true) { + IR::ID newName = getNewName(stateInfo); + if (checkBefore && newStates.count(newName)) { + return; + } + hasOutOfboundState = true; + newStates.insert(newName); + stateInfo->newState = new IR::ParserState(newName, + IR::IndexedVector(), + new IR::PathExpression(new IR::Type_State(), + new IR::Path(outOfBoundsStateName, false))); + } + + /// running symbolic execution + ParserInfo* run() { + synthesizedParser = new ParserInfo(); + auto initMap = initializeVariables(); + if (initMap == nullptr) + // error during initializer evaluation + return synthesizedParser; + auto startInfo = newStateInfo(nullptr, structure->start->name.name, initMap, 0); + structure->callsIndexes.emplace(structure->start->name.name, 0); + startInfo->scenarioStates.insert(structure->start->name.name); + std::vector toRun; // worklist + toRun.push_back(startInfo); + std::set visited; + std::unordered_set newStates; + while (!toRun.empty()) { + auto stateInfo = toRun.back(); + toRun.pop_back(); + LOG1("Symbolic evaluation of " << stateChain(stateInfo)); + // checking visited state, loop state, and the reachable states with needed header stack + // operators. + if (visited.count(VisitedKey(stateInfo)) && + !stateInfo->scenarioStates.count(stateInfo->name) && + !structure->reachableHSUsage(stateInfo->state->name, stateInfo)) + continue; + auto iHSNames = structure->statesWithHeaderStacks.find(stateInfo->name); + if (iHSNames != structure->statesWithHeaderStacks.end()) + stateInfo->scenarioHS.insert(iHSNames->second.begin(), iHSNames->second.end()); + visited.insert(VisitedKey(stateInfo)); // add to visited map + stateInfo->scenarioStates.insert(stateInfo->name); // add to loops detection + bool infLoop = checkLoops(stateInfo); + if (infLoop) { + // Stop unrolling if it was an error. + if (wasError) { + IR::ID newName = getNewName(stateInfo); + if (newStates.count(newName) != 0) { + evaluateState(stateInfo, newStates); + } + wasError = false; + continue; + } + // don't evaluate successors anymore + // generate call OutOfBound + addOutFoBound(stateInfo, newStates); + continue; + } + IR::ID newName = getNewName(stateInfo); + bool notAdded = newStates.count(newName) == 0; + auto nextStates = evaluateState(stateInfo, newStates); + if (nextStates.first == nullptr) { + if (nextStates.second && stateInfo->predecessor && + newName.name !=stateInfo->predecessor->newState->name) { + // generate call OutOfBound + addOutFoBound(stateInfo, newStates, false); + } else { + // save current state + if (notAdded) { + stateInfo->newState = stateInfo->state->clone(); + } + } + LOG1("No next states"); + continue; + } + toRun.insert(toRun.end(), nextStates.first->begin(), nextStates.first->end()); + } + + return synthesizedParser; + } +}; + +} // namespace ParserStructureImpl + +bool ParserStructure::analyze(ReferenceMap* refMap, TypeMap* typeMap, bool unroll, bool& wasError) { + ParserStructureImpl::ParserSymbolicInterpreter psi(this, refMap, typeMap, unroll, wasError); + result = psi.run(); + return psi.hasOutOfboundState; +} + +/// check reachability for usage of header stack +bool ParserStructure::reachableHSUsage(IR::ID id, const ParserStateInfo* state) const { + if (!state->scenarioHS.size()) + return false; + CHECK_NULL(callGraph); + const IR::IDeclaration* declaration = parser->states.getDeclaration(id.name); + BUG_CHECK(declaration && declaration->is(), "Invalid declaration %1%", id); + std::set reachableStates; + callGraph->reachable(declaration->to(), reachableStates); + std::set reachebleHSoperators; + for (auto i : reachableStates) { + auto iHSNames = statesWithHeaderStacks.find(i->name); + if (iHSNames != statesWithHeaderStacks.end()) + reachebleHSoperators.insert(iHSNames->second.begin(), iHSNames->second.end()); + } + std::set intersectionHSOperators; + std::set_intersection(state->scenarioHS.begin(), state->scenarioHS.end(), + reachebleHSoperators.begin(), reachebleHSoperators.end(), + std::inserter(intersectionHSOperators, + intersectionHSOperators.begin())); + return intersectionHSOperators.size() > 0; +} + +void ParserStructure::addStateHSUsage(const IR::ParserState* state, + const IR::Expression* expression) { + if (state == nullptr || expression == nullptr || !expression->type->is()) + return; + auto i = statesWithHeaderStacks.find(state->name.name); + if (i == statesWithHeaderStacks.end()) { + std::set s; + s.insert(expression->toString()); + statesWithHeaderStacks.emplace(state->name.name, s); + } else { + i->second.insert(expression->toString()); + } +} + + +} // namespace P4 diff --git a/midend/parserUnroll.h b/midend/parserUnroll.h index d15d40579a0..d145745c9c2 100644 --- a/midend/parserUnroll.h +++ b/midend/parserUnroll.h @@ -1,293 +1,296 @@ -/* -Copyright 2016 VMware, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#ifndef _MIDEND_PARSERUNROLL_H_ -#define _MIDEND_PARSERUNROLL_H_ - -#include - -#include "ir/ir.h" -#include "frontends/common/resolveReferences/referenceMap.h" -#include "frontends/p4/callGraph.h" -#include "frontends/p4/typeChecking/typeChecker.h" -#include "frontends/p4/typeMap.h" -#include "frontends/p4/simplify.h" -#include "interpreter.h" - -namespace P4 { - -/// Name of out of bound state -const char outOfBoundsStateName[] = "stateOutOfBound"; - -////////////////////////////////////////////// -// The following are for a single parser - -// Represents a variable for storing indexes values for a header stack. -// -// This is a thin wrapper around a 'const IR::Member*' to (1) enforce invariants on which forms of -// Members can represent state variables and (2) enable the use of StackVariable as map keys. -// -// A Member can represent a StackVariable exactly when its qualifying variable -// (IR::Member::expr) either is a PathExpression or can represent a StackVariable. -class StackVariable { - friend class StackVariableHash; - public: - /// Determines whether @expr can represent a StateVariable. - static bool repOk(const IR::Expression* expr); - - // Implements comparisons so that StateVariables can be used as map keys. - bool operator==(const StackVariable& other) const; - - private: - const IR::Expression* variable; - - public: - /// Implicitly converts IR::Expression* to a StackVariable. - StackVariable(const IR::Expression* expr); // NOLINT(runtime/explicit) -}; - -/// Class with hash function for @a StackVariable. -class StackVariableHash { - public: - size_t operator()(const StackVariable& var) const; -}; - -typedef std::unordered_map StackVariableMap; - -/// Information produced for a parser state by the symbolic evaluator -struct ParserStateInfo { - friend class ParserStateRewriter; - cstring name; // new state name - const IR::P4Parser* parser; - const IR::ParserState* state; // original state this is produced from - const ParserStateInfo* predecessor; // how we got here in the symbolic evaluation - ValueMap* before; - ValueMap* after; - IR::ParserState* newState; // pointer to a new state - size_t currentIndex; - StackVariableMap statesIndexes; // global map in state indexes - // set of parsers' states names with are in current path. - std::unordered_set scenarioStates; - std::unordered_set scenarioHS; // scenario header stack's operations - ParserStateInfo(cstring name, const IR::P4Parser* parser, const IR::ParserState* state, - const ParserStateInfo* predecessor, ValueMap* before, size_t index) : - name(name), parser(parser), state(state), predecessor(predecessor), - before(before), after(nullptr), newState(nullptr), currentIndex(index) { - CHECK_NULL(parser); CHECK_NULL(state); CHECK_NULL(before); - if (predecessor) { - statesIndexes = predecessor->statesIndexes; - scenarioHS = predecessor->scenarioHS; - } - } -}; - -/// Information produced for a parser by the symbolic evaluator -class ParserInfo { - friend class RewriteAllParsers; - // for each original state a vector of states produced by unrolling - std::map*> states; - public: - std::vector* get(cstring origState) { - std::vector *vec; - auto it = states.find(origState); - if (it == states.end()) { - vec = new std::vector; - states.emplace(origState, vec); - } else { - vec = it->second; - } - return vec; - } - void add(ParserStateInfo* si) { - cstring origState = si->state->name.name; - auto vec = get(origState); - vec->push_back(si); - } - std::map*>& getStates() { - return states; - } -}; - -typedef CallGraph StateCallGraph; - -/// Information about a parser in the input program -class ParserStructure { - friend class ParserStateRewriter; - friend class ParserSymbolicInterpreter; - friend class AnalyzeParser; - std::map stateMap; - - public: - const IR::P4Parser* parser; - const IR::ParserState* start; - const ParserInfo* result; - StateCallGraph* callGraph; - std::map > statesWithHeaderStacks; - std::map callsIndexes; // map for curent calls of state insite current one - void setParser(const IR::P4Parser* parser) { - CHECK_NULL(parser); - callGraph = new StateCallGraph(parser->name); - this->parser = parser; - start = nullptr; - } - void addState(const IR::ParserState* state) - { stateMap.emplace(state->name, state); } - const IR::ParserState* get(cstring state) const - { return ::get(stateMap, state); } - void calls(const IR::ParserState* caller, const IR::ParserState* callee) - { callGraph->calls(caller, callee); } - - bool analyze(ReferenceMap* refMap, TypeMap* typeMap, bool unroll, bool& wasError); - /// check reachability for usage of header stack - bool reachableHSUsage(IR::ID id, const ParserStateInfo* state) const; - - protected: - /// evaluates rechable states with HS operations for each path. - void evaluateReachability(); - /// add HS name which is used in a current state. - void addStateHSUsage(const IR::ParserState* state, const IR::Expression* expression); -}; - -class AnalyzeParser : public Inspector { - const ReferenceMap* refMap; - ParserStructure* current; - const IR::ParserState* currentState; - public: - AnalyzeParser(const ReferenceMap* refMap, ParserStructure* current) : - refMap(refMap), current(current), currentState(nullptr) { - CHECK_NULL(refMap); CHECK_NULL(current); setName("AnalyzeParser"); - visitDagOnce = false; - } - - bool preorder(const IR::P4Parser* parser) override { - LOG2("Scanning " << parser); - current->setParser(parser); - return true; - } - bool preorder(const IR::ParserState* state) override; - void postorder(const IR::ParserState* state) override; - void postorder(const IR::ArrayIndex* array) override; - void postorder(const IR::Member* member) override; - void postorder(const IR::PathExpression* expression) override; -}; - -// Applied to a P4Parser object. -class ParserRewriter : public PassManager { - ParserStructure current; - friend class RewriteAllParsers; - public: - bool hasOutOfboundState; - bool wasError; - ParserRewriter(ReferenceMap* refMap, - TypeMap* typeMap, bool unroll) { - CHECK_NULL(refMap); CHECK_NULL(typeMap); - wasError = false; - setName("ParserRewriter"); - addPasses({ - new AnalyzeParser(refMap, ¤t), - [this, refMap, typeMap, unroll](void) { - hasOutOfboundState = current.analyze(refMap, typeMap, unroll, wasError); }, - }); - } -}; - -/////////////////////////////////////////////////////// -// The following are applied to the entire program - -class RewriteAllParsers : public Transform { - ReferenceMap* refMap; - TypeMap* typeMap; - bool unroll; - - public: - RewriteAllParsers(ReferenceMap* refMap, TypeMap* typeMap, bool unroll) : - refMap(refMap), typeMap(typeMap), unroll(unroll) { - CHECK_NULL(refMap); CHECK_NULL(typeMap); setName("RewriteAllParsers"); - } - - // start generation of a code - const IR::Node* postorder(IR::P4Parser* parser) override { - // making rewriting - auto rewriter = new ParserRewriter(refMap, typeMap, unroll); - rewriter->setCalledBy(this); - parser->apply(*rewriter); - if (rewriter->wasError) { - return parser; - } - /// make a new parser - BUG_CHECK(rewriter->current.result, - "No result was found after unrolling of the parser loop"); - IR::P4Parser* newParser = parser->clone(); - IR::IndexedVector states = newParser->states; - newParser->states.clear(); - if (rewriter->hasOutOfboundState) { - // generating state with verify(false, error.StackOutOfBounds) - IR::Vector* arguments = new IR::Vector(); - arguments->push_back( - new IR::Argument(new IR::BoolLiteral(IR::Type::Boolean::get(), false))); - arguments->push_back(new IR::Argument(new IR::Member( - new IR::TypeNameExpression(new IR::Type_Name(IR::ID("error"))), - IR::ID("StackOutOfBounds")))); - IR::IndexedVector components; - IR::IndexedVector parameters; - parameters.push_back( - new IR::Parameter(IR::ID("check"), IR::Direction::In, IR::Type::Boolean::get())); - parameters.push_back(new IR::Parameter(IR::ID("toSignal"), IR::Direction::In, - new IR::Type_Name(IR::ID("error")))); - components.push_back(new IR::MethodCallStatement(new IR::MethodCallExpression( - IR::Type::Void::get(), - new IR::PathExpression( - new IR::Type_Method(IR::Type::Void::get(), new IR::ParameterList(parameters), - "*method"), - new IR::Path(IR::ID("verify"))), - arguments))); - auto* outOfBoundsState = new IR::ParserState( - IR::ID(outOfBoundsStateName), components, - new IR::PathExpression(new IR::Type_State(), - new IR::Path(IR::ParserState::reject, false))); - newParser->states.push_back(outOfBoundsState); - } - for (auto& i : rewriter->current.result->states) { - for (auto& j : *i.second) - if (j->newState) { - if (rewriter->hasOutOfboundState && - j->newState->name.name == "stateOutOfBound") { - continue; - } - newParser->states.push_back(j->newState); - } - } - // adding accept/reject - newParser->states.push_back(new IR::ParserState(IR::ParserState::accept, nullptr)); - newParser->states.push_back(new IR::ParserState(IR::ParserState::reject, nullptr)); - return newParser; - } -}; - -class ParsersUnroll : public PassManager { - public: - ParsersUnroll(bool unroll, ReferenceMap* refMap, TypeMap* typeMap) { - // remove block statements - passes.push_back(new SimplifyControlFlow(refMap, typeMap)); - passes.push_back(new TypeChecking(refMap, typeMap)); - passes.push_back(new RewriteAllParsers(refMap, typeMap, unroll)); - setName("ParsersUnroll"); - } -}; - -} // namespace P4 - -#endif /* _MIDEND_PARSERUNROLL_H_ */ +/* +Copyright 2016 VMware, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _MIDEND_PARSERUNROLL_H_ +#define _MIDEND_PARSERUNROLL_H_ + +#include + +#include "ir/ir.h" +#include "frontends/common/resolveReferences/referenceMap.h" +#include "frontends/p4/callGraph.h" +#include "frontends/p4/typeChecking/typeChecker.h" +#include "frontends/p4/typeMap.h" +#include "frontends/p4/simplify.h" +#include "interpreter.h" + +namespace P4 { + +/// Name of out of bound state +const char outOfBoundsStateName[] = "stateOutOfBound"; + +////////////////////////////////////////////// +// The following are for a single parser + +// Represents a variable for storing indexes values for a header stack. +// +// This is a thin wrapper around a 'const IR::Member*' to (1) enforce invariants on which forms of +// Members can represent state variables and (2) enable the use of StackVariable as map keys. +// +// A Member can represent a StackVariable exactly when its qualifying variable +// (IR::Member::expr) either is a PathExpression or can represent a StackVariable. +class StackVariable { + friend class StackVariableHash; + public: + /// Determines whether @expr can represent a StateVariable. + static bool repOk(const IR::Expression* expr); + + // Implements comparisons so that StateVariables can be used as map keys. + bool operator==(const StackVariable& other) const; + + private: + const IR::Expression* variable; + + public: + /// Implicitly converts IR::Expression* to a StackVariable. + StackVariable(const IR::Expression* expr); // NOLINT(runtime/explicit) +}; + +/// Class with hash function for @a StackVariable. +class StackVariableHash { + public: + size_t operator()(const StackVariable& var) const; +}; + +typedef std::unordered_map StackVariableMap; +typedef std::unordered_map StackVariableIndexMap; + +/// Information produced for a parser state by the symbolic evaluator +struct ParserStateInfo { + friend class ParserStateRewriter; + cstring name; // new state name + const IR::P4Parser* parser; + const IR::ParserState* state; // original state this is produced from + const ParserStateInfo* predecessor; // how we got here in the symbolic evaluation + ValueMap* before; + ValueMap* after; + IR::ParserState* newState; // pointer to a new state + size_t currentIndex; + StackVariableMap statesIndexes; // global map in state indexes + // set of parsers' states names with are in current path. + std::unordered_set scenarioStates; + std::unordered_set scenarioHS; // scenario header stack's operations + StackVariableIndexMap substitutedIndexes; // values of the evaluated indexes + ParserStateInfo(cstring name, const IR::P4Parser* parser, const IR::ParserState* state, + const ParserStateInfo* predecessor, ValueMap* before, size_t index) : + name(name), parser(parser), state(state), predecessor(predecessor), + before(before), after(nullptr), newState(nullptr), currentIndex(index) { + CHECK_NULL(parser); CHECK_NULL(state); CHECK_NULL(before); + if (predecessor) { + statesIndexes = predecessor->statesIndexes; + scenarioHS = predecessor->scenarioHS; + } + } +}; + +/// Information produced for a parser by the symbolic evaluator +class ParserInfo { + friend class RewriteAllParsers; + // for each original state a vector of states produced by unrolling + std::map*> states; + public: + std::vector* get(cstring origState) { + std::vector *vec; + auto it = states.find(origState); + if (it == states.end()) { + vec = new std::vector; + states.emplace(origState, vec); + } else { + vec = it->second; + } + return vec; + } + void add(ParserStateInfo* si) { + cstring origState = si->state->name.name; + auto vec = get(origState); + vec->push_back(si); + } + std::map*>& getStates() { + return states; + } +}; + +typedef CallGraph StateCallGraph; + +/// Information about a parser in the input program +class ParserStructure { + friend class ParserStateRewriter; + friend class ParserSymbolicInterpreter; + friend class AnalyzeParser; + std::map stateMap; + + public: + const IR::P4Parser* parser; + const IR::ParserState* start; + const ParserInfo* result; + StateCallGraph* callGraph; + std::map > statesWithHeaderStacks; + std::map callsIndexes; // map for curent calls of state insite current one + void setParser(const IR::P4Parser* parser) { + CHECK_NULL(parser); + callGraph = new StateCallGraph(parser->name); + this->parser = parser; + start = nullptr; + } + void addState(const IR::ParserState* state) + { stateMap.emplace(state->name, state); } + const IR::ParserState* get(cstring state) const + { return ::get(stateMap, state); } + void calls(const IR::ParserState* caller, const IR::ParserState* callee) + { callGraph->calls(caller, callee); } + + bool analyze(ReferenceMap* refMap, TypeMap* typeMap, bool unroll, bool& wasError); + /// check reachability for usage of header stack + bool reachableHSUsage(IR::ID id, const ParserStateInfo* state) const; + + protected: + /// evaluates rechable states with HS operations for each path. + void evaluateReachability(); + /// add HS name which is used in a current state. + void addStateHSUsage(const IR::ParserState* state, const IR::Expression* expression); +}; + +class AnalyzeParser : public Inspector { + const ReferenceMap* refMap; + ParserStructure* current; + const IR::ParserState* currentState; + public: + AnalyzeParser(const ReferenceMap* refMap, ParserStructure* current) : + refMap(refMap), current(current), currentState(nullptr) { + CHECK_NULL(refMap); CHECK_NULL(current); setName("AnalyzeParser"); + visitDagOnce = false; + } + + bool preorder(const IR::P4Parser* parser) override { + LOG2("Scanning " << parser); + current->setParser(parser); + return true; + } + bool preorder(const IR::ParserState* state) override; + void postorder(const IR::ParserState* state) override; + void postorder(const IR::ArrayIndex* array) override; + void postorder(const IR::Member* member) override; + void postorder(const IR::PathExpression* expression) override; +}; + +// Applied to a P4Parser object. +class ParserRewriter : public PassManager { + ParserStructure current; + friend class RewriteAllParsers; + public: + bool hasOutOfboundState; + bool wasError; + ParserRewriter(ReferenceMap* refMap, + TypeMap* typeMap, bool unroll) { + CHECK_NULL(refMap); CHECK_NULL(typeMap); + wasError = false; + setName("ParserRewriter"); + addPasses({ + new AnalyzeParser(refMap, ¤t), + [this, refMap, typeMap, unroll](void) { + hasOutOfboundState = current.analyze(refMap, typeMap, unroll, wasError); }, + }); + } +}; + +/////////////////////////////////////////////////////// +// The following are applied to the entire program + +class RewriteAllParsers : public Transform { + ReferenceMap* refMap; + TypeMap* typeMap; + bool unroll; + + public: + RewriteAllParsers(ReferenceMap* refMap, TypeMap* typeMap, bool unroll) : + refMap(refMap), typeMap(typeMap), unroll(unroll) { + CHECK_NULL(refMap); CHECK_NULL(typeMap); setName("RewriteAllParsers"); + } + + // start generation of a code + const IR::Node* postorder(IR::P4Parser* parser) override { + // making rewriting + auto rewriter = new ParserRewriter(refMap, typeMap, unroll); + rewriter->setCalledBy(this); + parser->apply(*rewriter); + if (rewriter->wasError) { + return parser; + } + /// make a new parser + BUG_CHECK(rewriter->current.result, + "No result was found after unrolling of the parser loop"); + IR::P4Parser* newParser = parser->clone(); + IR::IndexedVector states = newParser->states; + newParser->states.clear(); + if (rewriter->hasOutOfboundState) { + // generating state with verify(false, error.StackOutOfBounds) + IR::Vector* arguments = new IR::Vector(); + arguments->push_back( + new IR::Argument(new IR::BoolLiteral(IR::Type::Boolean::get(), false))); + arguments->push_back(new IR::Argument(new IR::Member( + new IR::TypeNameExpression(new IR::Type_Name(IR::ID("error"))), + IR::ID("StackOutOfBounds")))); + IR::IndexedVector components; + IR::IndexedVector parameters; + parameters.push_back( + new IR::Parameter(IR::ID("check"), IR::Direction::In, IR::Type::Boolean::get())); + parameters.push_back(new IR::Parameter(IR::ID("toSignal"), IR::Direction::In, + new IR::Type_Name(IR::ID("error")))); + components.push_back(new IR::MethodCallStatement(new IR::MethodCallExpression( + IR::Type::Void::get(), + new IR::PathExpression( + new IR::Type_Method(IR::Type::Void::get(), new IR::ParameterList(parameters), + "*method"), + new IR::Path(IR::ID("verify"))), + arguments))); + auto* outOfBoundsState = new IR::ParserState( + IR::ID(outOfBoundsStateName), components, + new IR::PathExpression(new IR::Type_State(), + new IR::Path(IR::ParserState::reject, false))); + newParser->states.push_back(outOfBoundsState); + } + for (auto& i : rewriter->current.result->states) { + for (auto& j : *i.second) + if (j->newState) { + if (rewriter->hasOutOfboundState && + j->newState->name.name == "stateOutOfBound") { + continue; + } + newParser->states.push_back(j->newState); + } + } + // adding accept/reject + newParser->states.push_back(new IR::ParserState(IR::ParserState::accept, nullptr)); + newParser->states.push_back(new IR::ParserState(IR::ParserState::reject, nullptr)); + return newParser; + } +}; + +class ParsersUnroll : public PassManager { + public: + ParsersUnroll(bool unroll, ReferenceMap* refMap, TypeMap* typeMap) { + // remove block statements + passes.push_back(new SimplifyControlFlow(refMap, typeMap)); + passes.push_back(new TypeChecking(refMap, typeMap)); + passes.push_back(new RewriteAllParsers(refMap, typeMap, unroll)); + setName("ParsersUnroll"); + } +}; + +} // namespace P4 + +#endif /* _MIDEND_PARSERUNROLL_H_ */ From 3f68b6719ce2c68ccfbb2bf732235ae94f143d28 Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Mon, 14 Nov 2022 13:06:25 +0200 Subject: [PATCH 06/12] update tests info --- .../parser-unroll-test9-first.p4 | 96 +++++++++++ .../parser-unroll-test9-frontend.p4 | 96 +++++++++++ .../parser-unroll-test9-midend.p4 | 102 +++++++++++ .../parser-unroll-test9.p4 | 96 +++++++++++ .../parser-unroll-test9.p4-stderr | 0 .../parser-unroll-test9.p4.entries.txt | 0 .../parser-unroll-test9.p4.p4info.txt | 3 + .../parser-unroll-issue3537-1-midend.p4 | 6 +- .../parser-unroll-issue3537-1.p4-stderr | 4 + .../parser-unroll-issue3537-midend.p4 | 18 +- .../parser-unroll-issue3537.p4-stderr | 8 - .../parser-unroll-test9-first.p4 | 96 +++++++++++ .../parser-unroll-test9-frontend.p4 | 96 +++++++++++ .../parser-unroll-test9-midend.p4 | 160 ++++++++++++++++++ .../parser-unroll/parser-unroll-test9.p4 | 96 +++++++++++ .../parser-unroll-test9.p4-stderr | 8 + 16 files changed, 865 insertions(+), 20 deletions(-) create mode 100644 testdata/p4_16_samples_outputs/parser-unroll-test9-first.p4 create mode 100644 testdata/p4_16_samples_outputs/parser-unroll-test9-frontend.p4 create mode 100644 testdata/p4_16_samples_outputs/parser-unroll-test9-midend.p4 create mode 100644 testdata/p4_16_samples_outputs/parser-unroll-test9.p4 create mode 100644 testdata/p4_16_samples_outputs/parser-unroll-test9.p4-stderr create mode 100644 testdata/p4_16_samples_outputs/parser-unroll-test9.p4.entries.txt create mode 100644 testdata/p4_16_samples_outputs/parser-unroll-test9.p4.p4info.txt create mode 100644 testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-first.p4 create mode 100644 testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-frontend.p4 create mode 100644 testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-midend.p4 create mode 100644 testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9.p4 create mode 100644 testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9.p4-stderr diff --git a/testdata/p4_16_samples_outputs/parser-unroll-test9-first.p4 b/testdata/p4_16_samples_outputs/parser-unroll-test9-first.p4 new file mode 100644 index 00000000000..7f11eaecace --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll-test9-first.p4 @@ -0,0 +1,96 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + pkt.extract(hdr.i); + hdr.i.counter = 8w0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.index) { + 8w0: mixed_finite_loop; + 8w1: mixed_infinite_loop; + 8w2: infinite_loop; + 8w3: finite_loop; + default: reject; + } + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i1) { + 8w2: accept; + default: finite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i2) { + 8w1: start_loops; + 8w2: accept; + } + } + state mixed_infinite_loop { + transition select(hdr.i.counter) { + 8w3: accept; + default: start_loops; + } + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.counter) { + 8w3: accept; + default: infinite_loop; + } + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/parser-unroll-test9-frontend.p4 b/testdata/p4_16_samples_outputs/parser-unroll-test9-frontend.p4 new file mode 100644 index 00000000000..7f11eaecace --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll-test9-frontend.p4 @@ -0,0 +1,96 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + pkt.extract(hdr.i); + hdr.i.counter = 8w0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.index) { + 8w0: mixed_finite_loop; + 8w1: mixed_infinite_loop; + 8w2: infinite_loop; + 8w3: finite_loop; + default: reject; + } + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i1) { + 8w2: accept; + default: finite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i2) { + 8w1: start_loops; + 8w2: accept; + } + } + state mixed_infinite_loop { + transition select(hdr.i.counter) { + 8w3: accept; + default: start_loops; + } + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.counter) { + 8w3: accept; + default: infinite_loop; + } + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/parser-unroll-test9-midend.p4 b/testdata/p4_16_samples_outputs/parser-unroll-test9-midend.p4 new file mode 100644 index 00000000000..a22d8ed8537 --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll-test9-midend.p4 @@ -0,0 +1,102 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + pkt.extract(hdr.i); + hdr.i.counter = 8w0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.index) { + 8w0: mixed_finite_loop; + 8w1: mixed_infinite_loop; + 8w2: infinite_loop; + 8w3: finite_loop; + default: reject; + } + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i1) { + 8w2: accept; + default: finite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i2) { + 8w1: start_loops; + 8w2: accept; + default: noMatch; + } + } + state mixed_infinite_loop { + transition select(hdr.i.counter) { + 8w3: accept; + default: start_loops; + } + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.counter) { + 8w3: accept; + default: infinite_loop; + } + } + state noMatch { + verify(false, error.NoMatch); + transition reject; + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h[0]); + pkt.emit(h.h[1]); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/parser-unroll-test9.p4 b/testdata/p4_16_samples_outputs/parser-unroll-test9.p4 new file mode 100644 index 00000000000..bd853e8cdce --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll-test9.p4 @@ -0,0 +1,96 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + pkt.extract(hdr.i); + hdr.i.counter = 0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 1; + transition select(hdr.i.index) { + 0: mixed_finite_loop; + 1: mixed_infinite_loop; + 2: infinite_loop; + 3: finite_loop; + default: reject; + } + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 1; + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i1) { + 2: accept; + default: finite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i2) { + 1: start_loops; + 2: accept; + } + } + state mixed_infinite_loop { + transition select(hdr.i.counter) { + 3: accept; + default: start_loops; + } + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 1; + transition select(hdr.i.counter) { + 3: accept; + default: infinite_loop; + } + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/parser-unroll-test9.p4-stderr b/testdata/p4_16_samples_outputs/parser-unroll-test9.p4-stderr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/testdata/p4_16_samples_outputs/parser-unroll-test9.p4.entries.txt b/testdata/p4_16_samples_outputs/parser-unroll-test9.p4.entries.txt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/testdata/p4_16_samples_outputs/parser-unroll-test9.p4.p4info.txt b/testdata/p4_16_samples_outputs/parser-unroll-test9.p4.p4info.txt new file mode 100644 index 00000000000..9ec92493e4c --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll-test9.p4.p4info.txt @@ -0,0 +1,3 @@ +pkg_info { + arch: "v1model" +} diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-1-midend.p4 b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-1-midend.p4 index 32eccc1d985..5ab4bf841b7 100644 --- a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-1-midend.p4 +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-1-midend.p4 @@ -11,9 +11,6 @@ struct M { parser ParserI(packet_in packet, out H hdr, inout M meta, inout standard_metadata_t smeta) { @name("ParserI.tmp_0") bit<16> tmp_0; @name("ParserI.tmp_2") bit<16> tmp_2; - state start { - transition s1; - } state s1 { packet.advance(32w16); tmp_0 = packet.lookahead>(); @@ -34,6 +31,9 @@ parser ParserI(packet_in packet, out H hdr, inout M meta, inout standard_metadat state s4 { transition accept; } + state start { + transition s1; + } } control IngressI(inout H hdr, inout M meta, inout standard_metadata_t smeta) { diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-1.p4-stderr b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-1.p4-stderr index cbc7f8caaf8..ac6d7790049 100644 --- a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-1.p4-stderr +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-1.p4-stderr @@ -1,2 +1,6 @@ [--Wwarn=invalid] warning: Parser cycle can't be unrolled, because ParserUnroll can't detect the number of loop iterations: Parser ParserI state chain: start, s1, s2, s2 +[--Wwarn=invalid] warning: Parser cycle can't be unrolled, because ParserUnroll can't detect the number of loop iterations: +Parser ParserI state chain: start, s1, s2, s1 +[--Wwarn=invalid] warning: Parser cycle can't be unrolled, because ParserUnroll can't detect the number of loop iterations: +Parser ParserI state chain: start, s1, s1 diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-midend.p4 b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-midend.p4 index 6ba6cdab74c..86396411c25 100644 --- a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-midend.p4 +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537-midend.p4 @@ -39,15 +39,6 @@ struct M { } parser ParserI(packet_in packet, out H hdr, inout M meta, inout standard_metadata_t smeta) { - state start { - packet.extract(hdr.h1); - packet.extract(hdr.h2); - transition select(hdr.h2.f1) { - 16w0x800: parse_ipv4; - 16w0x86dd: parse_ipv6; - default: accept; - } - } state parse_ipv4 { packet.extract(hdr.h3); transition select(hdr.h3.f2, hdr.h3.f3) { @@ -76,6 +67,15 @@ parser ParserI(packet_in packet, out H hdr, inout M meta, inout standard_metadat default: accept; } } + state start { + packet.extract(hdr.h1); + packet.extract(hdr.h2); + transition select(hdr.h2.f1) { + 16w0x800: parse_ipv4; + 16w0x86dd: parse_ipv6; + default: accept; + } + } } control IngressI(inout H hdr, inout M meta, inout standard_metadata_t smeta) { diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537.p4-stderr b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537.p4-stderr index d313aa617cd..e69de29bb2d 100644 --- a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537.p4-stderr +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-issue3537.p4-stderr @@ -1,8 +0,0 @@ -parser-unroll-issue3537.p4(62): [--Wwarn=ignore-prop] warning: Result of packet.extract is not defined: Exception: OverwritingHeader - packet.extract(hdr.h6); - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -parser-unroll-issue3537.p4(42): [--Wwarn=ignore-prop] warning: Result of packet.extract is not defined: Exception: OverwritingHeader - packet.extract(hdr.h3); - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -[--Wwarn=invalid] warning: Parser cycle can't be unrolled, because ParserUnroll can't detect the number of loop iterations: -Parser ParserI state chain: start, parse_ipv6, parse_udp, parse_ipv4, parse_udp, parse_ipv4, parse_udp diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-first.p4 b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-first.p4 new file mode 100644 index 00000000000..7f11eaecace --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-first.p4 @@ -0,0 +1,96 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + pkt.extract(hdr.i); + hdr.i.counter = 8w0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.index) { + 8w0: mixed_finite_loop; + 8w1: mixed_infinite_loop; + 8w2: infinite_loop; + 8w3: finite_loop; + default: reject; + } + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i1) { + 8w2: accept; + default: finite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i2) { + 8w1: start_loops; + 8w2: accept; + } + } + state mixed_infinite_loop { + transition select(hdr.i.counter) { + 8w3: accept; + default: start_loops; + } + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.counter) { + 8w3: accept; + default: infinite_loop; + } + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-frontend.p4 b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-frontend.p4 new file mode 100644 index 00000000000..7f11eaecace --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-frontend.p4 @@ -0,0 +1,96 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + pkt.extract(hdr.i); + hdr.i.counter = 8w0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.index) { + 8w0: mixed_finite_loop; + 8w1: mixed_infinite_loop; + 8w2: infinite_loop; + 8w3: finite_loop; + default: reject; + } + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i1) { + 8w2: accept; + default: finite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i2) { + 8w1: start_loops; + 8w2: accept; + } + } + state mixed_infinite_loop { + transition select(hdr.i.counter) { + 8w3: accept; + default: start_loops; + } + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.counter) { + 8w3: accept; + default: infinite_loop; + } + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-midend.p4 b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-midend.p4 new file mode 100644 index 00000000000..d309fd91739 --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-midend.p4 @@ -0,0 +1,160 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state stateOutOfBound { + verify(false, error.StackOutOfBounds); + transition reject; + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + pkt.extract(hdr.h[32w0]); + transition select(hdr.h[32w0].i1) { + 8w2: accept; + default: finite_loop1; + } + } + state finite_loop1 { + hdr.i.counter = hdr.i.counter + 8w1; + pkt.extract(hdr.h[32w1]); + transition select(hdr.h[32w1].i1) { + 8w2: accept; + default: finite_loop2; + } + } + state finite_loop2 { + transition stateOutOfBound; + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.counter) { + 8w3: accept; + default: infinite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h[32w0]); + transition select(hdr.h[32w0].i2) { + 8w1: start_loops1; + 8w2: accept; + default: noMatch; + } + } + state mixed_finite_loop1 { + pkt.extract(hdr.h[32w1]); + transition select(hdr.h[32w1].i2) { + 8w1: start_loops2; + 8w2: accept; + default: noMatch; + } + } + state mixed_finite_loop2 { + transition stateOutOfBound; + } + state mixed_infinite_loop { + transition select(hdr.i.counter) { + 8w3: accept; + default: start_loops; + } + } + state mixed_infinite_loop1 { + transition select(hdr.i.counter) { + 8w3: accept; + default: start_loops1; + } + } + state mixed_infinite_loop2 { + transition select(hdr.i.counter) { + 8w3: accept; + default: start_loops2; + } + } + state noMatch { + verify(false, error.NoMatch); + transition reject; + } + state start { + pkt.extract(hdr.i); + hdr.i.counter = 8w0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.index) { + 8w0: mixed_finite_loop; + 8w1: mixed_infinite_loop; + 8w2: infinite_loop; + 8w3: finite_loop; + default: reject; + } + } + state start_loops1 { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.index) { + 8w0: mixed_finite_loop1; + 8w1: mixed_infinite_loop1; + 8w2: infinite_loop; + 8w3: finite_loop1; + default: reject; + } + } + state start_loops2 { + hdr.i.counter = hdr.i.counter + 8w1; + transition select(hdr.i.index) { + 8w0: mixed_finite_loop2; + 8w1: mixed_infinite_loop2; + 8w2: infinite_loop; + 8w3: finite_loop2; + default: reject; + } + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h[0]); + pkt.emit(h.h[1]); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9.p4 b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9.p4 new file mode 100644 index 00000000000..bd853e8cdce --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9.p4 @@ -0,0 +1,96 @@ +#include +#define V1MODEL_VERSION 20180101 +#include + +header h_stack { + bit<8> i1; + bit<8> i2; +} + +header h_index { + bit<8> index; + bit<8> counter; +} + +struct headers { + h_stack[2] h; + h_index i; +} + +struct Meta { +} + +parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t sm) { + state start { + pkt.extract(hdr.i); + hdr.i.counter = 0; + transition start_loops; + } + state start_loops { + hdr.i.counter = hdr.i.counter + 1; + transition select(hdr.i.index) { + 0: mixed_finite_loop; + 1: mixed_infinite_loop; + 2: infinite_loop; + 3: finite_loop; + default: reject; + } + } + state finite_loop { + hdr.i.counter = hdr.i.counter + 1; + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i1) { + 2: accept; + default: finite_loop; + } + } + state mixed_finite_loop { + pkt.extract(hdr.h.next); + transition select(hdr.h.last.i2) { + 1: start_loops; + 2: accept; + } + } + state mixed_infinite_loop { + transition select(hdr.i.counter) { + 3: accept; + default: start_loops; + } + } + state infinite_loop { + hdr.i.counter = hdr.i.counter + 1; + transition select(hdr.i.counter) { + 3: accept; + default: infinite_loop; + } + } +} + +control ingress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control vrfy(inout headers h, inout Meta m) { + apply { + } +} + +control update(inout headers h, inout Meta m) { + apply { + } +} + +control egress(inout headers h, inout Meta m, inout standard_metadata_t sm) { + apply { + } +} + +control deparser(packet_out pkt, in headers h) { + apply { + pkt.emit(h.h); + pkt.emit(h.i); + } +} + +V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main; diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9.p4-stderr b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9.p4-stderr new file mode 100644 index 00000000000..6a8d84ee2a5 --- /dev/null +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9.p4-stderr @@ -0,0 +1,8 @@ +[--Wwarn=invalid] warning: Parser cycle can't be unrolled, because ParserUnroll can't detect the number of loop iterations: +Parser p state chain: start, start_loops, infinite_loop, infinite_loop +[--Wwarn=invalid] warning: Parser cycle can't be unrolled, because ParserUnroll can't detect the number of loop iterations: +Parser p state chain: start, start_loops, mixed_infinite_loop, start_loops +[--Wwarn=invalid] warning: Parser cycle can't be unrolled, because ParserUnroll can't detect the number of loop iterations: +Parser p state chain: start, start_loops, mixed_finite_loop, start_loops, mixed_infinite_loop, start_loops +[--Wwarn=invalid] warning: Parser cycle can't be unrolled, because ParserUnroll can't detect the number of loop iterations: +Parser p state chain: start, start_loops, mixed_finite_loop, start_loops, mixed_finite_loop, start_loops, mixed_infinite_loop, start_loops From 59c70f464acab68df60f8fbda7724f2ffaaff04a Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Mon, 14 Nov 2022 16:43:30 +0200 Subject: [PATCH 07/12] fix. bug for double application od PU to the 9th test --- midend/parserUnroll.h | 1 + 1 file changed, 1 insertion(+) diff --git a/midend/parserUnroll.h b/midend/parserUnroll.h index d145745c9c2..77a2450ec1d 100644 --- a/midend/parserUnroll.h +++ b/midend/parserUnroll.h @@ -93,6 +93,7 @@ struct ParserStateInfo { if (predecessor) { statesIndexes = predecessor->statesIndexes; scenarioHS = predecessor->scenarioHS; + substitutedIndexes = predecessor->substitutedIndexes; } } }; From 4a663be58a9e69565afdd0883e855ae9e7a9844d Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Mon, 14 Nov 2022 19:05:52 +0200 Subject: [PATCH 08/12] add failed test --- backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake b/backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake index 4767081a91f..e1308c800ec 100755 --- a/backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake +++ b/backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake @@ -90,6 +90,7 @@ p4tools_add_xfail_reason( p4tools_add_xfail_reason( "testgen-p4c-bmv2" "differs|Expected ([0-9]+) packets on port ([0-9]+) got ([0-9]+)" + parser-unroll-test9.p4 ) p4tools_add_xfail_reason( From 58a805eaeb33b11cd56e999e9b78fd6d09899ec5 Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Thu, 17 Nov 2022 17:11:29 +0200 Subject: [PATCH 09/12] fix. bug for OutOfBound state --- .../testgen/targets/bmv2/test/BMV2Xfail.cmake | 1 - midend/parserUnroll.cpp | 29 ++++++++++--------- .../parser-unroll-test9-midend.p4 | 1 + 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake b/backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake index e1308c800ec..4767081a91f 100755 --- a/backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake +++ b/backends/p4tools/testgen/targets/bmv2/test/BMV2Xfail.cmake @@ -90,7 +90,6 @@ p4tools_add_xfail_reason( p4tools_add_xfail_reason( "testgen-p4c-bmv2" "differs|Expected ([0-9]+) packets on port ([0-9]+) got ([0-9]+)" - parser-unroll-test9.p4 ) p4tools_add_xfail_reason( diff --git a/midend/parserUnroll.cpp b/midend/parserUnroll.cpp index ff23fff5bd1..9493a0d7104 100644 --- a/midend/parserUnroll.cpp +++ b/midend/parserUnroll.cpp @@ -719,7 +719,8 @@ class ParserSymbolicInterpreter { return IR::ID(state->state->name + std::to_string(state->currentIndex)); } - using EvaluationStateResult = std::pair*, bool>; + using EvaluationStateResult = std::tuple*, bool, + IR::IndexedVector>; /// Generates new state with the help of symbolic execution. /// If corresponded state was generated previously then it returns @a nullptr and false. @@ -733,13 +734,13 @@ class ParserSymbolicInterpreter { if (unroll) { newName = getNewName(state); if (newStates.count(newName)) - return EvaluationStateResult(nullptr, false); + return EvaluationStateResult(nullptr, false, components); newStates.insert(newName); } for (auto s : state->state->components) { auto* newComponent = executeStatement(state, s, valueMap); if (!newComponent) - return EvaluationStateResult(nullptr, true); + return EvaluationStateResult(nullptr, true, components); if (unroll) components.push_back(newComponent); } @@ -747,7 +748,7 @@ class ParserSymbolicInterpreter { auto result = evaluateSelect(state, valueMap); if (unroll) { if (result.second == nullptr) { - return EvaluationStateResult(nullptr, true); + return EvaluationStateResult(nullptr, true, components); } if (state->name == newName) { state->newState = new IR::ParserState(state->state->srcInfo, newName, @@ -758,7 +759,7 @@ class ParserSymbolicInterpreter { new IR::ParserState(state->state->srcInfo, newName, components, result.second); } } - return EvaluationStateResult(result.first, true); + return EvaluationStateResult(result.first, true, components); } public: @@ -775,8 +776,9 @@ class ParserSymbolicInterpreter { } /// generate call OutOfBound - void addOutFoBound(ParserStateInfo* stateInfo, std::unordered_set& newStates, - bool checkBefore = true) { + void addOutOfBound(ParserStateInfo* stateInfo, std::unordered_set& newStates, + bool checkBefore = true, IR::IndexedVector components = + IR::IndexedVector()) { IR::ID newName = getNewName(stateInfo); if (checkBefore && newStates.count(newName)) { return; @@ -784,8 +786,7 @@ class ParserSymbolicInterpreter { hasOutOfboundState = true; newStates.insert(newName); stateInfo->newState = new IR::ParserState(newName, - IR::IndexedVector(), - new IR::PathExpression(new IR::Type_State(), + components, new IR::PathExpression(new IR::Type_State(), new IR::Path(outOfBoundsStateName, false))); } @@ -831,17 +832,17 @@ class ParserSymbolicInterpreter { } // don't evaluate successors anymore // generate call OutOfBound - addOutFoBound(stateInfo, newStates); + addOutOfBound(stateInfo, newStates); continue; } IR::ID newName = getNewName(stateInfo); bool notAdded = newStates.count(newName) == 0; auto nextStates = evaluateState(stateInfo, newStates); - if (nextStates.first == nullptr) { - if (nextStates.second && stateInfo->predecessor && + if (get<0>(nextStates) == nullptr) { + if (get<1>(nextStates) && stateInfo->predecessor && newName.name !=stateInfo->predecessor->newState->name) { // generate call OutOfBound - addOutFoBound(stateInfo, newStates, false); + addOutOfBound(stateInfo, newStates, false, get<2>(nextStates)); } else { // save current state if (notAdded) { @@ -851,7 +852,7 @@ class ParserSymbolicInterpreter { LOG1("No next states"); continue; } - toRun.insert(toRun.end(), nextStates.first->begin(), nextStates.first->end()); + toRun.insert(toRun.end(), get<0>(nextStates)->begin(), get<0>(nextStates)->end()); } return synthesizedParser; diff --git a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-midend.p4 b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-midend.p4 index d309fd91739..4dc4054dfc8 100644 --- a/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-midend.p4 +++ b/testdata/p4_16_samples_outputs/parser-unroll/parser-unroll-test9-midend.p4 @@ -42,6 +42,7 @@ parser p(packet_in pkt, out headers hdr, inout Meta m, inout standard_metadata_t } } state finite_loop2 { + hdr.i.counter = hdr.i.counter + 8w1; transition stateOutOfBound; } state infinite_loop { From 70e02297510959ed5c6f334ac2071f884861d7d6 Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Thu, 17 Nov 2022 17:23:14 +0200 Subject: [PATCH 10/12] update --- midend/parserUnroll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/midend/parserUnroll.cpp b/midend/parserUnroll.cpp index 9493a0d7104..957a1cd9397 100644 --- a/midend/parserUnroll.cpp +++ b/midend/parserUnroll.cpp @@ -777,7 +777,7 @@ class ParserSymbolicInterpreter { /// generate call OutOfBound void addOutOfBound(ParserStateInfo* stateInfo, std::unordered_set& newStates, - bool checkBefore = true, IR::IndexedVector components = + bool checkBefore = true, IR::IndexedVector components = IR::IndexedVector()) { IR::ID newName = getNewName(stateInfo); if (checkBefore && newStates.count(newName)) { From d557dcc87ed4c9fd3cbed0546278107202ee5c3d Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Mon, 21 Nov 2022 15:25:28 +0200 Subject: [PATCH 11/12] update --- midend/parserUnroll.cpp | 1816 +++++++++++++++++++-------------------- midend/parserUnroll.h | 594 ++++++------- 2 files changed, 1205 insertions(+), 1205 deletions(-) diff --git a/midend/parserUnroll.cpp b/midend/parserUnroll.cpp index 957a1cd9397..cc1ba0b753f 100644 --- a/midend/parserUnroll.cpp +++ b/midend/parserUnroll.cpp @@ -1,908 +1,908 @@ -#include "parserUnroll.h" -#include "interpreter.h" -#include "lib/hash.h" -#include "lib/stringify.h" -#include "ir/ir.h" - -namespace P4 { - -StackVariable::StackVariable(const IR::Expression* expr) : variable(expr) { - CHECK_NULL(expr); - BUG_CHECK(repOk(expr), "Invalid stack variable %1%", expr); - variable = expr; -} - -bool StackVariable::repOk(const IR::Expression* expr) { - // Only members and path expression can be stack variables. - const auto* member = expr->to(); - if (member == nullptr) { - return expr->is(); - } - - // A member is a stack variable if it is qualified by a PathExpression or if its qualifier is a - // stack variable. - return member->expr->is() || repOk(member->expr); -} - -bool StackVariable::operator==(const StackVariable& other) const { - // Delegate to IR's notion of equality. - return variable->equiv(*other.variable); -} - -size_t StackVariableHash::operator()(const StackVariable& var) const { - // hash for path expression. - if (const auto* path = var.variable->to()) { - return Util::Hash::fnv1a(path->path->name.name); - } - const IR::Member* curMember = var.variable->to(); - std::vector h; - while (curMember) { - h.push_back(Util::Hash::fnv1a(curMember->member.name)); - if (auto* path = curMember->expr->to()) { - h.push_back(Util::Hash::fnv1a(path->path->name)); - break; - } - curMember = curMember->expr->checkedTo(); - } - return Util::Hash::fnv1a(h.data(), sizeof(size_t) * h.size()); -} - -/// The main class for parsers' states key for visited checking. -struct VisitedKey { - cstring name; // name of a state. - StackVariableMap indexes; // indexes of header stacks. - - VisitedKey(cstring name, StackVariableMap& indexes) - : name(name), indexes(indexes) {} - - explicit VisitedKey(const ParserStateInfo* stateInfo) { - CHECK_NULL(stateInfo); - name = stateInfo->state->name.name; - indexes = stateInfo->statesIndexes; - } - - /// Checks of two states to be less. If @a name < @a e.name then it returns @a true. - /// if @a name > @a e.name then it returns false. - /// If @a name is equal @a e.name then it checks the values of the headers stack indexes. - /// For both map of header stacks' indexes it creates map of pairs of values for each - /// header stack index. - /// If some indexes are missing then it cosiders them as -1. - /// Next, it continues checking each pair in a intermidiate map with the same approach - /// as for @a name and @a e.name. - bool operator<(const VisitedKey& e) const { - if (name < e.name) - return true; - if (name > e.name) - return false; - std::unordered_map, StackVariableHash > mp; - for (auto& i1 : indexes) { - mp.emplace(i1.first, std::make_pair(i1.second, -1)); - } - for (auto& i2 : e.indexes) { - auto ref = mp.find(i2.first); - if (ref == mp.end()) - mp.emplace(i2.first, std::make_pair(-1, i2.second)); - else - ref->second.second = i2.second; - } - for (auto j : mp) { - if (j.second.first < j.second.second) - return true; - if (j.second.first > j.second.second) - return false; - } - return false; - } -}; - -/** - * Checks for terminal state. - */ -bool isTerminalState(IR::ID id) { - return (id.name == IR::ParserState::reject || id.name == IR::ParserState::accept); -} - -bool AnalyzeParser::preorder(const IR::ParserState* state) { - LOG1("Found state " << dbp(state) << " of " << current->parser->name); - if (state->name.name == IR::ParserState::start) - current->start = state; - current->addState(state); - currentState = state; - return true; -} - -void AnalyzeParser::postorder(const IR::ParserState*) { - currentState = nullptr; -} - -void AnalyzeParser::postorder(const IR::ArrayIndex* array) { - // ignore arrays with concrete arguments - if (array->right->is()) - return; - // tries to collect the name of a header stack for current state of a parser. - current->addStateHSUsage(currentState, array->left); -} - -void AnalyzeParser::postorder(const IR::Member* member) { - // tries to collect the name of a header stack for current state of a parser. - current->addStateHSUsage(currentState, member->expr); -} - -void AnalyzeParser::postorder(const IR::PathExpression* expression) { - auto state = findContext(); - if (state == nullptr) - return; - auto decl = refMap->getDeclaration(expression->path); - if (decl->is()) - current->calls(state, decl->to()); -} - -namespace ParserStructureImpl { - -/// Visited map of pairs : -/// 1) name of the parser state and values of the header stack indexes. -/// 2) value of index which is used for generation of the new names of the parsers' states. -using StatesVisitedMap = std::map; - -// Makes transformation of the statements of a parser state. -// It updates indexes of a header stack and generates correct name of the next transition. -class ParserStateRewriter : public Transform { - public: - /// Default constructor. - ParserStateRewriter(ParserStructure* parserStructure, ParserStateInfo* state, - ValueMap* valueMap, ReferenceMap* refMap, TypeMap* typeMap, - ExpressionEvaluator* afterExec, StatesVisitedMap& visitedStates) : - parserStructure(parserStructure), state(state), - valueMap(valueMap), refMap(refMap), typeMap(typeMap), afterExec(afterExec), - visitedStates(visitedStates), wasOutOfBound(false) { - CHECK_NULL(parserStructure); CHECK_NULL(state); - CHECK_NULL(refMap); CHECK_NULL(typeMap); - CHECK_NULL(parserStructure); CHECK_NULL(state); - currentIndex = 0; - } - - /// Updates indexes of a header stack. - IR::Node* preorder(IR::ArrayIndex* expression) { - ParserStateRewriter rewriter(parserStructure, state, valueMap, refMap, typeMap, - afterExec, visitedStates); - auto basetype = getTypeArray(expression->left); - if (!basetype->is()) - return expression; - IR::ArrayIndex* newExpression = expression->clone(); - ExpressionEvaluator ev(refMap, typeMap, valueMap); - auto* value = ev.evaluate(expression->right, false); - if (!value->is()) - return expression; - auto* res = value->to()->constant->clone(); - newExpression->right = res; - if (!res->fitsInt64()) { - // we need to leave expression as is. - ::warning(ErrorType::ERR_EXPRESSION, "Index can't be concretized : %1%", - expression); - return expression; - } - const auto* arrayType = basetype->to(); - if (res->asUnsigned() >= arrayType->getSize()) { - wasOutOfBound = true; - return expression; - } - state->substitutedIndexes[newExpression->left] = res; - return newExpression; - } - - /// Eliminates header stack acces next, last operations. - IR::Node* postorder(IR::Member* expression) { - if (!afterExec) - return expression; - auto basetype = getTypeArray(getOriginal()->expr); - if (basetype->is()) { - auto l = afterExec->get(expression->expr); - BUG_CHECK(l->is(), "%1%: expected an array", l); - auto array = l->to(); - unsigned idx = 0; - unsigned offset = 0; - if (state->statesIndexes.count(expression->expr)) { - idx = state->statesIndexes.at(expression->expr); - if (expression->member.name != IR::Type_Stack::last) { - offset = 1; - } - } - if (expression->member.name == IR::Type_Stack::lastIndex) { - return new IR::Constant(IR::Type_Bits::get(32), idx); - } else { - if (idx + offset >= array->size) { - wasOutOfBound = true; - return expression; - } - state->statesIndexes[expression->expr] = idx + offset; - return new IR::ArrayIndex(expression->expr->clone(), - new IR::Constant(IR::Type_Bits::get(32), idx + offset)); - } - } - return expression; - } - - /// Adds a new index for transition if it is required by algorithm. - IR::Node* postorder(IR::PathExpression* expression) { - if (!expression->type->is()) - return expression; - IR::ID newName = genNewName(expression->path->name); - if (newName.name == expression->path->name.name) // first call - return expression; - // need to change name - return new IR::PathExpression(expression->type, new IR::Path(newName, false)); - } - inline size_t getIndex() { return currentIndex; } - bool isOutOfBound() {return wasOutOfBound;} - - protected: - const IR::Type* getTypeArray(const IR::Node* element) { - if (element->is()) { - const IR::Expression* left = element->to()->left; - if (left->type->is()) - return left->type->to()->elementType; - } - return typeMap->getType(element, true); - } - - /// Checks if this state was called previously with the same state of header stack indexes. - /// If it was called then it returns true and generates a new name with the stored index. - bool was_called(cstring nm, IR::ID& id) { - if (state->scenarioStates.count(id.name) && state->statesIndexes.size() == 0) - return false; - auto i = visitedStates.find(VisitedKey(nm, state->statesIndexes)); - if (i == visitedStates.end()) - return false; - if (i->second > 0) - id = IR::ID(id.name + std::to_string(i->second)); - currentIndex = i->second; - return true; - } - - /// Checks values of the headers stacks which were evaluated. - bool checkIndexes(const StackVariableIndexMap& prev, const StackVariableIndexMap& cur) { - if (prev.size() != cur.size()) { - return false; - } - for (auto& i : prev) { - auto j = cur.find(i.first); - if (j == cur.end()) { - return false; - } - if (!i.second->equiv(*j->second)) { - return false; - } - } - return true; - } - - /// Returns true for current id if indexes of the headers stack - /// are the same before previous call of the same parser state. - bool calledWithNoChanges(IR::ID id, const ParserStateInfo* state) { - CHECK_NULL(state); - const auto* prevState = state; - while (prevState->state->name.name != id.name) { - prevState = prevState->predecessor; - if (prevState == nullptr) { - return false; - } - } - if (prevState->predecessor != nullptr) { - prevState = prevState->predecessor; - } else if (prevState == state && state->predecessor == nullptr) { - // The map should be empty if no next operators in a start. - return state->statesIndexes.empty(); - } - return prevState->statesIndexes == state->statesIndexes && - checkIndexes(prevState->substitutedIndexes, state->substitutedIndexes); - } - - /// Generated new state name - IR::ID genNewName(IR::ID id) { - if (isTerminalState(id)) - return id; - size_t index = 0; - cstring name = id.name; - if (parserStructure->callsIndexes.count(id.name) && (state->scenarioStates.count(id.name) - || parserStructure->reachableHSUsage(id, state))) { - // or self called with no extraction... - // in this case we need to use the same name as it was in previous call - if (was_called(name, id)) - return id; - if (calledWithNoChanges(id, state)) { - index = 0; - if (parserStructure->callsIndexes.count(id.name)) { - index = parserStructure->callsIndexes[id.name]; - } - if (index > 0) { - id = IR::ID(id.name + std::to_string(index)); - } - } else { - index = parserStructure->callsIndexes[id.name]; - parserStructure->callsIndexes[id.name] = index + 1; - if (index + 1 > 0) { - index++; - id = IR::ID(id.name + std::to_string(index)); - } - } - } else if (!parserStructure->callsIndexes.count(id.name)) { - index = 0; - parserStructure->callsIndexes[id.name] = 0; - } - currentIndex = index; - visitedStates.emplace(VisitedKey(name, state->statesIndexes), index); - return id; - } - - private: - ParserStructure* parserStructure; - ParserStateInfo* state; - ValueMap* valueMap; - ReferenceMap* refMap; - TypeMap* typeMap; - ExpressionEvaluator* afterExec; - StatesVisitedMap& visitedStates; - size_t currentIndex; - bool wasOutOfBound; -}; - -class ParserSymbolicInterpreter { - friend class ParserStateRewriter; - - protected: - ParserStructure* structure; - const IR::P4Parser* parser; - ReferenceMap* refMap; - TypeMap* typeMap; - SymbolicValueFactory* factory; - ParserInfo* synthesizedParser; // output produced - bool unroll; - StatesVisitedMap visitedStates; - bool& wasError; - - ValueMap* initializeVariables() { - wasError = false; - ValueMap* result = new ValueMap(); - ExpressionEvaluator ev(refMap, typeMap, result); - - for (auto p : parser->getApplyParameters()->parameters) { - auto type = typeMap->getType(p); - bool initialized = p->direction == IR::Direction::In || - p->direction == IR::Direction::InOut; - auto value = factory->create(type, !initialized); - result->set(p, value); - } - for (auto d : parser->parserLocals) { - auto type = typeMap->getType(d); - SymbolicValue* value = nullptr; - if (d->is()) { - auto dc = d->to(); - value = ev.evaluate(dc->initializer, false); - } else if (d->is()) { - auto dv = d->to(); - if (dv->initializer != nullptr) - value = ev.evaluate(dv->initializer, false); - } else if (d->is()) { - // The midend symbolic interpreter does not have - // a representation for value_set. - continue; - } - - if (value == nullptr) - value = factory->create(type, true); - if (value->is()) { - ::warning(ErrorType::ERR_EXPRESSION, - "%1%: %2%", d, value->to()->message()); - return nullptr; - } - if (value != nullptr) - result->set(d, value); - } - return result; - } - - ParserStateInfo* newStateInfo(const ParserStateInfo* predecessor, - cstring stateName, ValueMap* values, size_t index) { - if (stateName == IR::ParserState::accept || - stateName == IR::ParserState::reject) - return nullptr; - auto state = structure->get(stateName); - auto pi = new ParserStateInfo(stateName, parser, state, predecessor, values->clone(), - index); - synthesizedParser->add(pi); - return pi; - } - - static void stateChain(const ParserStateInfo* state, std::stringstream& stream) { - if (state->predecessor != nullptr) { - stateChain(state->predecessor, stream); - stream << ", "; - } - stream << state->state->externalName(); - } - - static cstring stateChain(const ParserStateInfo* state) { - CHECK_NULL(state); - std::stringstream result; - result << "Parser " << state->parser->externalName() << " state chain: "; - stateChain(state, result); - return result.str(); - } - - /// Return false if an error can be detected statically - bool reportIfError(const ParserStateInfo* state, SymbolicValue* value) const { - if (value->is()) { - auto exc = value->to(); - - bool stateClone = false; - auto orig = state->state; - auto crt = state; - while (crt->predecessor != nullptr) { - crt = crt->predecessor; - if (crt->state == orig) { - stateClone = true; - break; - } - } - - if (!stateClone) - // errors in the original state are signalled - ::warning(ErrorType::ERR_EXPRESSION, "%1%: error %2% will be triggered\n%3%", - exc->errorPosition, exc->message(), stateChain(state)); - // else this error will occur in a clone of the state produced - // by unrolling - if the state is reached. So we don't give an error. - return false; - } - if (!value->is()) - return true; - return false; - } - - /// Executes symbolically the specified statement. - /// Returns pointer to generated statement if execution completes successfully, - /// and 'nullptr' if an error occurred. - const IR::StatOrDecl* executeStatement(ParserStateInfo* state, const IR::StatOrDecl* sord, - ValueMap* valueMap) { - const IR::StatOrDecl* newSord = nullptr; - ExpressionEvaluator ev(refMap, typeMap, valueMap); - - SymbolicValue* errorValue = nullptr; - bool success = true; - if (sord->is()) { - auto ass = sord->to(); - auto left = ev.evaluate(ass->left, true); - errorValue = left; - success = reportIfError(state, left); - if (success) { - auto right = ev.evaluate(ass->right, false); - errorValue = right; - success = reportIfError(state, right); - if (success) - left->assign(right); - } - } else if (sord->is()) { - // can have side-effects - auto mc = sord->to(); - auto e = ev.evaluate(mc->methodCall, false); - errorValue = e; - success = reportIfError(state, e); - } else if (auto bs = sord->to()) { - IR::IndexedVector newComponents; - for (auto* component : bs->components) { - auto newComponent = executeStatement(state, component, valueMap); - if (!newComponent) - success = false; - else - newComponents.push_back(newComponent); - } - sord = new IR::BlockStatement(newComponents); - } else { - BUG("%1%: unexpected declaration or statement", sord); - } - if (!success) { - if (errorValue->is()) { - auto* exc = errorValue->to(); - if (exc->exc == P4::StandardExceptions::StackOutOfBounds) { - return newSord; - } - } - std::stringstream errorStr; - errorStr << errorValue; - ::warning(ErrorType::WARN_IGNORE_PROPERTY, - "Result of %1% is not defined: %2%", sord, errorStr.str()); - } - ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, &ev, - visitedStates); - const IR::Node* node = sord->apply(rewriter); - if (rewriter.isOutOfBound()) { - return nullptr; - } - newSord = node->to(); - LOG2("After " << sord << " state is\n" << valueMap); - return newSord; - } - - using EvaluationSelectResult = std::pair*, - const IR::Expression*>; - - EvaluationSelectResult evaluateSelect(ParserStateInfo* state, - ValueMap* valueMap) { - const IR::Expression* newSelect = nullptr; - auto select = state->state->selectExpression; - if (select == nullptr) - return EvaluationSelectResult(nullptr, nullptr); - - auto result = new std::vector(); - if (select->is()) { - auto path = select->to()->path; - auto next = refMap->getDeclaration(path); - BUG_CHECK(next->is(), "%1%: expected a state", path); - // update call indexes - ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, nullptr, - visitedStates); - const IR::Expression* node = select->apply(rewriter); - if (rewriter.isOutOfBound()) { - return EvaluationSelectResult(nullptr, nullptr); - } - CHECK_NULL(node); - newSelect = node->to(); - CHECK_NULL(newSelect); - auto nextInfo = newStateInfo(state, next->getName(), state->after, rewriter.getIndex()); - if (nextInfo != nullptr) { - nextInfo->scenarioStates = state->scenarioStates; - result->push_back(nextInfo); - } - } else if (select->is()) { - // TODO: really try to match cases; today we are conservative - auto se = select->to(); - IR::Vector newSelectCases; - ExpressionEvaluator ev(refMap, typeMap, valueMap); - try { - ev.evaluate(se->select, true); - } - catch (...) { - // Ignore throws from evaluator. - // If an index of a header stack is not substituted then - // we should leave a state as is. - } - ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, &ev, - visitedStates); - const IR::Node* node = se->select->apply(rewriter); - if (rewriter.isOutOfBound()) { - return EvaluationSelectResult(nullptr, nullptr); - } - const IR::ListExpression* newListSelect = node->to(); - auto etalonStateIndexes = state->statesIndexes; - for (auto c : se->selectCases) { - auto currentStateIndexes = etalonStateIndexes; - auto path = c->state->path; - auto next = refMap->getDeclaration(path); - BUG_CHECK(next->is(), "%1%: expected a state", path); - - // update call indexes - ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, - nullptr, visitedStates); - const IR::Node* node = c->apply(rewriter); - if (rewriter.isOutOfBound()) { - return EvaluationSelectResult(nullptr, nullptr); - } - CHECK_NULL(node); - auto newC = node->to(); - CHECK_NULL(newC); - newSelectCases.push_back(newC); - - auto nextInfo = newStateInfo(state, next->getName(), state->after, - rewriter.getIndex()); - if (nextInfo != nullptr) { - nextInfo->scenarioStates = state->scenarioStates; - nextInfo->statesIndexes = currentStateIndexes; - result->push_back(nextInfo); - } - } - newSelect = new IR::SelectExpression(newListSelect, newSelectCases); - } else { - BUG("%1%: unexpected expression", select); - } - return EvaluationSelectResult(result, newSelect); - } - - static bool headerValidityChanged(const SymbolicValue* first, const SymbolicValue* second) { - CHECK_NULL(first); CHECK_NULL(second); - if (first->is()) { - auto fhdr = first->to(); - auto shdr = second->to(); - CHECK_NULL(shdr); - return !fhdr->valid->equals(shdr->valid); - } else if (first->is()) { - auto farray = first->to(); - auto sarray = second->to(); - CHECK_NULL(sarray); - for (size_t i = 0; i < farray->size; i++) { - auto hdr = farray->get(nullptr, i); - auto newHdr = sarray->get(nullptr, i); - if (headerValidityChanged(hdr, newHdr)) - return true; - } - } else if (first->is()) { - auto fstruct = first->to(); - auto sstruct = second->to(); - CHECK_NULL(sstruct); - for (auto f : fstruct->fieldValue) { - auto ffield = fstruct->get(nullptr, f.first); - auto sfield = sstruct->get(nullptr, f.first); - if (headerValidityChanged(ffield, sfield)) - return true; - } - } - return false; - } - - /// True if any header has changed its "validity" bit - static bool headerValidityChange(const ValueMap* before, const ValueMap* after) { - for (auto v : before->map) { - auto value = v.second; - if (headerValidityChanged(value, after->get(v.first))) - return true; - } - return false; - } - - /// True if both structures are equal. - bool equStackVariableMap(const StackVariableMap& l, const StackVariableMap& r) const { - if (l.empty()) { - return r.empty(); - } - for (const auto& i : l) { - const auto j = r.find(i.first); - if (j == r.end() || i.second != j->second) { - return false; - } - } - return true; - } - - /// Return true if we have detected a loop we cannot unroll - bool checkLoops(ParserStateInfo* state) const { - const ParserStateInfo* crt = state; - while (true) { - crt = crt->predecessor; - if (crt == nullptr) - break; - if (crt->state == state->state) { - // Loop detected. - // Check if any packet in the valueMap has changed - auto filter = [](const IR::IDeclaration*, const SymbolicValue* value) - { return value->is(); }; - auto packets = state->before->filter(filter); - auto prevPackets = crt->before->filter(filter); - if (packets->equals(prevPackets)) { - for (auto p : state->before->map) { - if (p.second->is()) { - auto pkt = p.second->to(); - if (pkt->isConservative()) { - break; - } - } - } - if (equStackVariableMap(crt->statesIndexes, state->statesIndexes)) { - ::warning(ErrorType::ERR_INVALID, - "Parser cycle can't be unrolled, because ParserUnroll can't " - "detect the number of loop iterations:\n%1%", - stateChain(state)); - wasError = true; - } - return true; - } - - // If no header validity has changed we can't really unroll - if (!headerValidityChange(crt->before, state->before)) { - if (equStackVariableMap(crt->statesIndexes, state->statesIndexes)) { - ::warning(ErrorType::ERR_INVALID, - "Parser cycle can't be unrolled, because ParserUnroll can't " - "detect the number of loop iterations:\n%1%", - stateChain(state)); - wasError = true; - } - return true; - } - break; - } - } - return false; - } - - /// Gets new name for a state - IR::ID getNewName(ParserStateInfo* state) { - if (state->currentIndex == 0) { - return state->state->name; - } - return IR::ID(state->state->name + std::to_string(state->currentIndex)); - } - - using EvaluationStateResult = std::tuple*, bool, - IR::IndexedVector>; - - /// Generates new state with the help of symbolic execution. - /// If corresponded state was generated previously then it returns @a nullptr and false. - /// @param newStates is a set of parsers' names which were genereted. - EvaluationStateResult evaluateState(ParserStateInfo* state, - std::unordered_set &newStates) { - LOG1("Analyzing " << dbp(state->state)); - auto valueMap = state->before->clone(); - IR::IndexedVector components; - IR::ID newName; - if (unroll) { - newName = getNewName(state); - if (newStates.count(newName)) - return EvaluationStateResult(nullptr, false, components); - newStates.insert(newName); - } - for (auto s : state->state->components) { - auto* newComponent = executeStatement(state, s, valueMap); - if (!newComponent) - return EvaluationStateResult(nullptr, true, components); - if (unroll) - components.push_back(newComponent); - } - state->after = valueMap; - auto result = evaluateSelect(state, valueMap); - if (unroll) { - if (result.second == nullptr) { - return EvaluationStateResult(nullptr, true, components); - } - if (state->name == newName) { - state->newState = new IR::ParserState(state->state->srcInfo, newName, - state->state->annotations, components, - result.second); - } else { - state->newState = - new IR::ParserState(state->state->srcInfo, newName, components, result.second); - } - } - return EvaluationStateResult(result.first, true, components); - } - - public: - bool hasOutOfboundState; - /// constructor - ParserSymbolicInterpreter(ParserStructure* structure, ReferenceMap* refMap, TypeMap* typeMap, - bool unroll, bool& wasError) : structure(structure), refMap(refMap), - typeMap(typeMap), synthesizedParser(nullptr), unroll(unroll), - wasError(wasError) { - CHECK_NULL(structure); CHECK_NULL(refMap); CHECK_NULL(typeMap); - factory = new SymbolicValueFactory(typeMap); - parser = structure->parser; - hasOutOfboundState = false; - } - - /// generate call OutOfBound - void addOutOfBound(ParserStateInfo* stateInfo, std::unordered_set& newStates, - bool checkBefore = true, IR::IndexedVector components = - IR::IndexedVector()) { - IR::ID newName = getNewName(stateInfo); - if (checkBefore && newStates.count(newName)) { - return; - } - hasOutOfboundState = true; - newStates.insert(newName); - stateInfo->newState = new IR::ParserState(newName, - components, new IR::PathExpression(new IR::Type_State(), - new IR::Path(outOfBoundsStateName, false))); - } - - /// running symbolic execution - ParserInfo* run() { - synthesizedParser = new ParserInfo(); - auto initMap = initializeVariables(); - if (initMap == nullptr) - // error during initializer evaluation - return synthesizedParser; - auto startInfo = newStateInfo(nullptr, structure->start->name.name, initMap, 0); - structure->callsIndexes.emplace(structure->start->name.name, 0); - startInfo->scenarioStates.insert(structure->start->name.name); - std::vector toRun; // worklist - toRun.push_back(startInfo); - std::set visited; - std::unordered_set newStates; - while (!toRun.empty()) { - auto stateInfo = toRun.back(); - toRun.pop_back(); - LOG1("Symbolic evaluation of " << stateChain(stateInfo)); - // checking visited state, loop state, and the reachable states with needed header stack - // operators. - if (visited.count(VisitedKey(stateInfo)) && - !stateInfo->scenarioStates.count(stateInfo->name) && - !structure->reachableHSUsage(stateInfo->state->name, stateInfo)) - continue; - auto iHSNames = structure->statesWithHeaderStacks.find(stateInfo->name); - if (iHSNames != structure->statesWithHeaderStacks.end()) - stateInfo->scenarioHS.insert(iHSNames->second.begin(), iHSNames->second.end()); - visited.insert(VisitedKey(stateInfo)); // add to visited map - stateInfo->scenarioStates.insert(stateInfo->name); // add to loops detection - bool infLoop = checkLoops(stateInfo); - if (infLoop) { - // Stop unrolling if it was an error. - if (wasError) { - IR::ID newName = getNewName(stateInfo); - if (newStates.count(newName) != 0) { - evaluateState(stateInfo, newStates); - } - wasError = false; - continue; - } - // don't evaluate successors anymore - // generate call OutOfBound - addOutOfBound(stateInfo, newStates); - continue; - } - IR::ID newName = getNewName(stateInfo); - bool notAdded = newStates.count(newName) == 0; - auto nextStates = evaluateState(stateInfo, newStates); - if (get<0>(nextStates) == nullptr) { - if (get<1>(nextStates) && stateInfo->predecessor && - newName.name !=stateInfo->predecessor->newState->name) { - // generate call OutOfBound - addOutOfBound(stateInfo, newStates, false, get<2>(nextStates)); - } else { - // save current state - if (notAdded) { - stateInfo->newState = stateInfo->state->clone(); - } - } - LOG1("No next states"); - continue; - } - toRun.insert(toRun.end(), get<0>(nextStates)->begin(), get<0>(nextStates)->end()); - } - - return synthesizedParser; - } -}; - -} // namespace ParserStructureImpl - -bool ParserStructure::analyze(ReferenceMap* refMap, TypeMap* typeMap, bool unroll, bool& wasError) { - ParserStructureImpl::ParserSymbolicInterpreter psi(this, refMap, typeMap, unroll, wasError); - result = psi.run(); - return psi.hasOutOfboundState; -} - -/// check reachability for usage of header stack -bool ParserStructure::reachableHSUsage(IR::ID id, const ParserStateInfo* state) const { - if (!state->scenarioHS.size()) - return false; - CHECK_NULL(callGraph); - const IR::IDeclaration* declaration = parser->states.getDeclaration(id.name); - BUG_CHECK(declaration && declaration->is(), "Invalid declaration %1%", id); - std::set reachableStates; - callGraph->reachable(declaration->to(), reachableStates); - std::set reachebleHSoperators; - for (auto i : reachableStates) { - auto iHSNames = statesWithHeaderStacks.find(i->name); - if (iHSNames != statesWithHeaderStacks.end()) - reachebleHSoperators.insert(iHSNames->second.begin(), iHSNames->second.end()); - } - std::set intersectionHSOperators; - std::set_intersection(state->scenarioHS.begin(), state->scenarioHS.end(), - reachebleHSoperators.begin(), reachebleHSoperators.end(), - std::inserter(intersectionHSOperators, - intersectionHSOperators.begin())); - return intersectionHSOperators.size() > 0; -} - -void ParserStructure::addStateHSUsage(const IR::ParserState* state, - const IR::Expression* expression) { - if (state == nullptr || expression == nullptr || !expression->type->is()) - return; - auto i = statesWithHeaderStacks.find(state->name.name); - if (i == statesWithHeaderStacks.end()) { - std::set s; - s.insert(expression->toString()); - statesWithHeaderStacks.emplace(state->name.name, s); - } else { - i->second.insert(expression->toString()); - } -} - - -} // namespace P4 +#include "parserUnroll.h" +#include "interpreter.h" +#include "lib/hash.h" +#include "lib/stringify.h" +#include "ir/ir.h" + +namespace P4 { + +StackVariable::StackVariable(const IR::Expression* expr) : variable(expr) { + CHECK_NULL(expr); + BUG_CHECK(repOk(expr), "Invalid stack variable %1%", expr); + variable = expr; +} + +bool StackVariable::repOk(const IR::Expression* expr) { + // Only members and path expression can be stack variables. + const auto* member = expr->to(); + if (member == nullptr) { + return expr->is(); + } + + // A member is a stack variable if it is qualified by a PathExpression or if its qualifier is a + // stack variable. + return member->expr->is() || repOk(member->expr); +} + +bool StackVariable::operator==(const StackVariable& other) const { + // Delegate to IR's notion of equality. + return variable->equiv(*other.variable); +} + +size_t StackVariableHash::operator()(const StackVariable& var) const { + // hash for path expression. + if (const auto* path = var.variable->to()) { + return Util::Hash::fnv1a(path->path->name.name); + } + const IR::Member* curMember = var.variable->to(); + std::vector h; + while (curMember) { + h.push_back(Util::Hash::fnv1a(curMember->member.name)); + if (auto* path = curMember->expr->to()) { + h.push_back(Util::Hash::fnv1a(path->path->name)); + break; + } + curMember = curMember->expr->checkedTo(); + } + return Util::Hash::fnv1a(h.data(), sizeof(size_t) * h.size()); +} + +/// The main class for parsers' states key for visited checking. +struct VisitedKey { + cstring name; // name of a state. + StackVariableMap indexes; // indexes of header stacks. + + VisitedKey(cstring name, StackVariableMap& indexes) + : name(name), indexes(indexes) {} + + explicit VisitedKey(const ParserStateInfo* stateInfo) { + CHECK_NULL(stateInfo); + name = stateInfo->state->name.name; + indexes = stateInfo->statesIndexes; + } + + /// Checks of two states to be less. If @a name < @a e.name then it returns @a true. + /// if @a name > @a e.name then it returns false. + /// If @a name is equal @a e.name then it checks the values of the headers stack indexes. + /// For both map of header stacks' indexes it creates map of pairs of values for each + /// header stack index. + /// If some indexes are missing then it cosiders them as -1. + /// Next, it continues checking each pair in a intermidiate map with the same approach + /// as for @a name and @a e.name. + bool operator<(const VisitedKey& e) const { + if (name < e.name) + return true; + if (name > e.name) + return false; + std::unordered_map, StackVariableHash > mp; + for (auto& i1 : indexes) { + mp.emplace(i1.first, std::make_pair(i1.second, -1)); + } + for (auto& i2 : e.indexes) { + auto ref = mp.find(i2.first); + if (ref == mp.end()) + mp.emplace(i2.first, std::make_pair(-1, i2.second)); + else + ref->second.second = i2.second; + } + for (auto j : mp) { + if (j.second.first < j.second.second) + return true; + if (j.second.first > j.second.second) + return false; + } + return false; + } +}; + +/** + * Checks for terminal state. + */ +bool isTerminalState(IR::ID id) { + return (id.name == IR::ParserState::reject || id.name == IR::ParserState::accept); +} + +bool AnalyzeParser::preorder(const IR::ParserState* state) { + LOG1("Found state " << dbp(state) << " of " << current->parser->name); + if (state->name.name == IR::ParserState::start) + current->start = state; + current->addState(state); + currentState = state; + return true; +} + +void AnalyzeParser::postorder(const IR::ParserState*) { + currentState = nullptr; +} + +void AnalyzeParser::postorder(const IR::ArrayIndex* array) { + // ignore arrays with concrete arguments + if (array->right->is()) + return; + // tries to collect the name of a header stack for current state of a parser. + current->addStateHSUsage(currentState, array->left); +} + +void AnalyzeParser::postorder(const IR::Member* member) { + // tries to collect the name of a header stack for current state of a parser. + current->addStateHSUsage(currentState, member->expr); +} + +void AnalyzeParser::postorder(const IR::PathExpression* expression) { + auto state = findContext(); + if (state == nullptr) + return; + auto decl = refMap->getDeclaration(expression->path); + if (decl->is()) + current->calls(state, decl->to()); +} + +namespace ParserStructureImpl { + +/// Visited map of pairs : +/// 1) name of the parser state and values of the header stack indexes. +/// 2) value of index which is used for generation of the new names of the parsers' states. +using StatesVisitedMap = std::map; + +// Makes transformation of the statements of a parser state. +// It updates indexes of a header stack and generates correct name of the next transition. +class ParserStateRewriter : public Transform { + public: + /// Default constructor. + ParserStateRewriter(ParserStructure* parserStructure, ParserStateInfo* state, + ValueMap* valueMap, ReferenceMap* refMap, TypeMap* typeMap, + ExpressionEvaluator* afterExec, StatesVisitedMap& visitedStates) : + parserStructure(parserStructure), state(state), + valueMap(valueMap), refMap(refMap), typeMap(typeMap), afterExec(afterExec), + visitedStates(visitedStates), wasOutOfBound(false) { + CHECK_NULL(parserStructure); CHECK_NULL(state); + CHECK_NULL(refMap); CHECK_NULL(typeMap); + CHECK_NULL(parserStructure); CHECK_NULL(state); + currentIndex = 0; + } + + /// Updates indexes of a header stack. + IR::Node* preorder(IR::ArrayIndex* expression) { + ParserStateRewriter rewriter(parserStructure, state, valueMap, refMap, typeMap, + afterExec, visitedStates); + auto basetype = getTypeArray(expression->left); + if (!basetype->is()) + return expression; + IR::ArrayIndex* newExpression = expression->clone(); + ExpressionEvaluator ev(refMap, typeMap, valueMap); + auto* value = ev.evaluate(expression->right, false); + if (!value->is()) + return expression; + auto* res = value->to()->constant->clone(); + newExpression->right = res; + if (!res->fitsInt64()) { + // we need to leave expression as is. + ::warning(ErrorType::ERR_EXPRESSION, "Index can't be concretized : %1%", + expression); + return expression; + } + const auto* arrayType = basetype->to(); + if (res->asUnsigned() >= arrayType->getSize()) { + wasOutOfBound = true; + return expression; + } + state->substitutedIndexes[newExpression->left] = res; + return newExpression; + } + + /// Eliminates header stack acces next, last operations. + IR::Node* postorder(IR::Member* expression) { + if (!afterExec) + return expression; + auto basetype = getTypeArray(getOriginal()->expr); + if (basetype->is()) { + auto l = afterExec->get(expression->expr); + BUG_CHECK(l->is(), "%1%: expected an array", l); + auto array = l->to(); + unsigned idx = 0; + unsigned offset = 0; + if (state->statesIndexes.count(expression->expr)) { + idx = state->statesIndexes.at(expression->expr); + if (expression->member.name != IR::Type_Stack::last) { + offset = 1; + } + } + if (expression->member.name == IR::Type_Stack::lastIndex) { + return new IR::Constant(IR::Type_Bits::get(32), idx); + } else { + if (idx + offset >= array->size) { + wasOutOfBound = true; + return expression; + } + state->statesIndexes[expression->expr] = idx + offset; + return new IR::ArrayIndex(expression->expr->clone(), + new IR::Constant(IR::Type_Bits::get(32), idx + offset)); + } + } + return expression; + } + + /// Adds a new index for transition if it is required by algorithm. + IR::Node* postorder(IR::PathExpression* expression) { + if (!expression->type->is()) + return expression; + IR::ID newName = genNewName(expression->path->name); + if (newName.name == expression->path->name.name) // first call + return expression; + // need to change name + return new IR::PathExpression(expression->type, new IR::Path(newName, false)); + } + inline size_t getIndex() { return currentIndex; } + bool isOutOfBound() {return wasOutOfBound;} + + protected: + const IR::Type* getTypeArray(const IR::Node* element) { + if (element->is()) { + const IR::Expression* left = element->to()->left; + if (left->type->is()) + return left->type->to()->elementType; + } + return typeMap->getType(element, true); + } + + /// Checks if this state was called previously with the same state of header stack indexes. + /// If it was called then it returns true and generates a new name with the stored index. + bool was_called(cstring nm, IR::ID& id) { + if (state->scenarioStates.count(id.name) && state->statesIndexes.size() == 0) + return false; + auto i = visitedStates.find(VisitedKey(nm, state->statesIndexes)); + if (i == visitedStates.end()) + return false; + if (i->second > 0) + id = IR::ID(id.name + std::to_string(i->second)); + currentIndex = i->second; + return true; + } + + /// Checks values of the headers stacks which were evaluated. + bool checkIndexes(const StackVariableIndexMap& prev, const StackVariableIndexMap& cur) { + if (prev.size() != cur.size()) { + return false; + } + for (auto& i : prev) { + auto j = cur.find(i.first); + if (j == cur.end()) { + return false; + } + if (!i.second->equiv(*j->second)) { + return false; + } + } + return true; + } + + /// Returns true for current id if indexes of the headers stack + /// are the same before previous call of the same parser state. + bool calledWithNoChanges(IR::ID id, const ParserStateInfo* state) { + CHECK_NULL(state); + const auto* prevState = state; + while (prevState->state->name.name != id.name) { + prevState = prevState->predecessor; + if (prevState == nullptr) { + return false; + } + } + if (prevState->predecessor != nullptr) { + prevState = prevState->predecessor; + } else if (prevState == state && state->predecessor == nullptr) { + // The map should be empty if no next operators in a start. + return state->statesIndexes.empty(); + } + return prevState->statesIndexes == state->statesIndexes && + checkIndexes(prevState->substitutedIndexes, state->substitutedIndexes); + } + + /// Generated new state name + IR::ID genNewName(IR::ID id) { + if (isTerminalState(id)) + return id; + size_t index = 0; + cstring name = id.name; + if (parserStructure->callsIndexes.count(id.name) && (state->scenarioStates.count(id.name) + || parserStructure->reachableHSUsage(id, state))) { + // or self called with no extraction... + // in this case we need to use the same name as it was in previous call + if (was_called(name, id)) + return id; + if (calledWithNoChanges(id, state)) { + index = 0; + if (parserStructure->callsIndexes.count(id.name)) { + index = parserStructure->callsIndexes[id.name]; + } + if (index > 0) { + id = IR::ID(id.name + std::to_string(index)); + } + } else { + index = parserStructure->callsIndexes[id.name]; + parserStructure->callsIndexes[id.name] = index + 1; + if (index + 1 > 0) { + index++; + id = IR::ID(id.name + std::to_string(index)); + } + } + } else if (!parserStructure->callsIndexes.count(id.name)) { + index = 0; + parserStructure->callsIndexes[id.name] = 0; + } + currentIndex = index; + visitedStates.emplace(VisitedKey(name, state->statesIndexes), index); + return id; + } + + private: + ParserStructure* parserStructure; + ParserStateInfo* state; + ValueMap* valueMap; + ReferenceMap* refMap; + TypeMap* typeMap; + ExpressionEvaluator* afterExec; + StatesVisitedMap& visitedStates; + size_t currentIndex; + bool wasOutOfBound; +}; + +class ParserSymbolicInterpreter { + friend class ParserStateRewriter; + + protected: + ParserStructure* structure; + const IR::P4Parser* parser; + ReferenceMap* refMap; + TypeMap* typeMap; + SymbolicValueFactory* factory; + ParserInfo* synthesizedParser; // output produced + bool unroll; + StatesVisitedMap visitedStates; + bool& wasError; + + ValueMap* initializeVariables() { + wasError = false; + ValueMap* result = new ValueMap(); + ExpressionEvaluator ev(refMap, typeMap, result); + + for (auto p : parser->getApplyParameters()->parameters) { + auto type = typeMap->getType(p); + bool initialized = p->direction == IR::Direction::In || + p->direction == IR::Direction::InOut; + auto value = factory->create(type, !initialized); + result->set(p, value); + } + for (auto d : parser->parserLocals) { + auto type = typeMap->getType(d); + SymbolicValue* value = nullptr; + if (d->is()) { + auto dc = d->to(); + value = ev.evaluate(dc->initializer, false); + } else if (d->is()) { + auto dv = d->to(); + if (dv->initializer != nullptr) + value = ev.evaluate(dv->initializer, false); + } else if (d->is()) { + // The midend symbolic interpreter does not have + // a representation for value_set. + continue; + } + + if (value == nullptr) + value = factory->create(type, true); + if (value->is()) { + ::warning(ErrorType::ERR_EXPRESSION, + "%1%: %2%", d, value->to()->message()); + return nullptr; + } + if (value != nullptr) + result->set(d, value); + } + return result; + } + + ParserStateInfo* newStateInfo(const ParserStateInfo* predecessor, + cstring stateName, ValueMap* values, size_t index) { + if (stateName == IR::ParserState::accept || + stateName == IR::ParserState::reject) + return nullptr; + auto state = structure->get(stateName); + auto pi = new ParserStateInfo(stateName, parser, state, predecessor, values->clone(), + index); + synthesizedParser->add(pi); + return pi; + } + + static void stateChain(const ParserStateInfo* state, std::stringstream& stream) { + if (state->predecessor != nullptr) { + stateChain(state->predecessor, stream); + stream << ", "; + } + stream << state->state->externalName(); + } + + static cstring stateChain(const ParserStateInfo* state) { + CHECK_NULL(state); + std::stringstream result; + result << "Parser " << state->parser->externalName() << " state chain: "; + stateChain(state, result); + return result.str(); + } + + /// Return false if an error can be detected statically + bool reportIfError(const ParserStateInfo* state, SymbolicValue* value) const { + if (value->is()) { + auto exc = value->to(); + + bool stateClone = false; + auto orig = state->state; + auto crt = state; + while (crt->predecessor != nullptr) { + crt = crt->predecessor; + if (crt->state == orig) { + stateClone = true; + break; + } + } + + if (!stateClone) + // errors in the original state are signalled + ::warning(ErrorType::ERR_EXPRESSION, "%1%: error %2% will be triggered\n%3%", + exc->errorPosition, exc->message(), stateChain(state)); + // else this error will occur in a clone of the state produced + // by unrolling - if the state is reached. So we don't give an error. + return false; + } + if (!value->is()) + return true; + return false; + } + + /// Executes symbolically the specified statement. + /// Returns pointer to generated statement if execution completes successfully, + /// and 'nullptr' if an error occurred. + const IR::StatOrDecl* executeStatement(ParserStateInfo* state, const IR::StatOrDecl* sord, + ValueMap* valueMap) { + const IR::StatOrDecl* newSord = nullptr; + ExpressionEvaluator ev(refMap, typeMap, valueMap); + + SymbolicValue* errorValue = nullptr; + bool success = true; + if (sord->is()) { + auto ass = sord->to(); + auto left = ev.evaluate(ass->left, true); + errorValue = left; + success = reportIfError(state, left); + if (success) { + auto right = ev.evaluate(ass->right, false); + errorValue = right; + success = reportIfError(state, right); + if (success) + left->assign(right); + } + } else if (sord->is()) { + // can have side-effects + auto mc = sord->to(); + auto e = ev.evaluate(mc->methodCall, false); + errorValue = e; + success = reportIfError(state, e); + } else if (auto bs = sord->to()) { + IR::IndexedVector newComponents; + for (auto* component : bs->components) { + auto newComponent = executeStatement(state, component, valueMap); + if (!newComponent) + success = false; + else + newComponents.push_back(newComponent); + } + sord = new IR::BlockStatement(newComponents); + } else { + BUG("%1%: unexpected declaration or statement", sord); + } + if (!success) { + if (errorValue->is()) { + auto* exc = errorValue->to(); + if (exc->exc == P4::StandardExceptions::StackOutOfBounds) { + return newSord; + } + } + std::stringstream errorStr; + errorStr << errorValue; + ::warning(ErrorType::WARN_IGNORE_PROPERTY, + "Result of %1% is not defined: %2%", sord, errorStr.str()); + } + ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, &ev, + visitedStates); + const IR::Node* node = sord->apply(rewriter); + if (rewriter.isOutOfBound()) { + return nullptr; + } + newSord = node->to(); + LOG2("After " << sord << " state is\n" << valueMap); + return newSord; + } + + using EvaluationSelectResult = std::pair*, + const IR::Expression*>; + + EvaluationSelectResult evaluateSelect(ParserStateInfo* state, + ValueMap* valueMap) { + const IR::Expression* newSelect = nullptr; + auto select = state->state->selectExpression; + if (select == nullptr) + return EvaluationSelectResult(nullptr, nullptr); + + auto result = new std::vector(); + if (select->is()) { + auto path = select->to()->path; + auto next = refMap->getDeclaration(path); + BUG_CHECK(next->is(), "%1%: expected a state", path); + // update call indexes + ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, nullptr, + visitedStates); + const IR::Expression* node = select->apply(rewriter); + if (rewriter.isOutOfBound()) { + return EvaluationSelectResult(nullptr, nullptr); + } + CHECK_NULL(node); + newSelect = node->to(); + CHECK_NULL(newSelect); + auto nextInfo = newStateInfo(state, next->getName(), state->after, rewriter.getIndex()); + if (nextInfo != nullptr) { + nextInfo->scenarioStates = state->scenarioStates; + result->push_back(nextInfo); + } + } else if (select->is()) { + // TODO: really try to match cases; today we are conservative + auto se = select->to(); + IR::Vector newSelectCases; + ExpressionEvaluator ev(refMap, typeMap, valueMap); + try { + ev.evaluate(se->select, true); + } + catch (...) { + // Ignore throws from evaluator. + // If an index of a header stack is not substituted then + // we should leave a state as is. + } + ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, &ev, + visitedStates); + const IR::Node* node = se->select->apply(rewriter); + if (rewriter.isOutOfBound()) { + return EvaluationSelectResult(nullptr, nullptr); + } + const IR::ListExpression* newListSelect = node->to(); + auto etalonStateIndexes = state->statesIndexes; + for (auto c : se->selectCases) { + auto currentStateIndexes = etalonStateIndexes; + auto path = c->state->path; + auto next = refMap->getDeclaration(path); + BUG_CHECK(next->is(), "%1%: expected a state", path); + + // update call indexes + ParserStateRewriter rewriter(structure, state, valueMap, refMap, typeMap, + nullptr, visitedStates); + const IR::Node* node = c->apply(rewriter); + if (rewriter.isOutOfBound()) { + return EvaluationSelectResult(nullptr, nullptr); + } + CHECK_NULL(node); + auto newC = node->to(); + CHECK_NULL(newC); + newSelectCases.push_back(newC); + + auto nextInfo = newStateInfo(state, next->getName(), state->after, + rewriter.getIndex()); + if (nextInfo != nullptr) { + nextInfo->scenarioStates = state->scenarioStates; + nextInfo->statesIndexes = currentStateIndexes; + result->push_back(nextInfo); + } + } + newSelect = new IR::SelectExpression(newListSelect, newSelectCases); + } else { + BUG("%1%: unexpected expression", select); + } + return EvaluationSelectResult(result, newSelect); + } + + static bool headerValidityChanged(const SymbolicValue* first, const SymbolicValue* second) { + CHECK_NULL(first); CHECK_NULL(second); + if (first->is()) { + auto fhdr = first->to(); + auto shdr = second->to(); + CHECK_NULL(shdr); + return !fhdr->valid->equals(shdr->valid); + } else if (first->is()) { + auto farray = first->to(); + auto sarray = second->to(); + CHECK_NULL(sarray); + for (size_t i = 0; i < farray->size; i++) { + auto hdr = farray->get(nullptr, i); + auto newHdr = sarray->get(nullptr, i); + if (headerValidityChanged(hdr, newHdr)) + return true; + } + } else if (first->is()) { + auto fstruct = first->to(); + auto sstruct = second->to(); + CHECK_NULL(sstruct); + for (auto f : fstruct->fieldValue) { + auto ffield = fstruct->get(nullptr, f.first); + auto sfield = sstruct->get(nullptr, f.first); + if (headerValidityChanged(ffield, sfield)) + return true; + } + } + return false; + } + + /// True if any header has changed its "validity" bit + static bool headerValidityChange(const ValueMap* before, const ValueMap* after) { + for (auto v : before->map) { + auto value = v.second; + if (headerValidityChanged(value, after->get(v.first))) + return true; + } + return false; + } + + /// True if both structures are equal. + bool equStackVariableMap(const StackVariableMap& l, const StackVariableMap& r) const { + if (l.empty()) { + return r.empty(); + } + for (const auto& i : l) { + const auto j = r.find(i.first); + if (j == r.end() || i.second != j->second) { + return false; + } + } + return true; + } + + /// Return true if we have detected a loop we cannot unroll + bool checkLoops(ParserStateInfo* state) const { + const ParserStateInfo* crt = state; + while (true) { + crt = crt->predecessor; + if (crt == nullptr) + break; + if (crt->state == state->state) { + // Loop detected. + // Check if any packet in the valueMap has changed + auto filter = [](const IR::IDeclaration*, const SymbolicValue* value) + { return value->is(); }; + auto packets = state->before->filter(filter); + auto prevPackets = crt->before->filter(filter); + if (packets->equals(prevPackets)) { + for (auto p : state->before->map) { + if (p.second->is()) { + auto pkt = p.second->to(); + if (pkt->isConservative()) { + break; + } + } + } + if (equStackVariableMap(crt->statesIndexes, state->statesIndexes)) { + ::warning(ErrorType::ERR_INVALID, + "Parser cycle can't be unrolled, because ParserUnroll can't " + "detect the number of loop iterations:\n%1%", + stateChain(state)); + wasError = true; + } + return true; + } + + // If no header validity has changed we can't really unroll + if (!headerValidityChange(crt->before, state->before)) { + if (equStackVariableMap(crt->statesIndexes, state->statesIndexes)) { + ::warning(ErrorType::ERR_INVALID, + "Parser cycle can't be unrolled, because ParserUnroll can't " + "detect the number of loop iterations:\n%1%", + stateChain(state)); + wasError = true; + } + return true; + } + break; + } + } + return false; + } + + /// Gets new name for a state + IR::ID getNewName(ParserStateInfo* state) { + if (state->currentIndex == 0) { + return state->state->name; + } + return IR::ID(state->state->name + std::to_string(state->currentIndex)); + } + + using EvaluationStateResult = std::tuple*, bool, + IR::IndexedVector>; + + /// Generates new state with the help of symbolic execution. + /// If corresponded state was generated previously then it returns @a nullptr and false. + /// @param newStates is a set of parsers' names which were genereted. + EvaluationStateResult evaluateState(ParserStateInfo* state, + std::unordered_set &newStates) { + LOG1("Analyzing " << dbp(state->state)); + auto valueMap = state->before->clone(); + IR::IndexedVector components; + IR::ID newName; + if (unroll) { + newName = getNewName(state); + if (newStates.count(newName)) + return EvaluationStateResult(nullptr, false, components); + newStates.insert(newName); + } + for (auto s : state->state->components) { + auto* newComponent = executeStatement(state, s, valueMap); + if (!newComponent) + return EvaluationStateResult(nullptr, true, components); + if (unroll) + components.push_back(newComponent); + } + state->after = valueMap; + auto result = evaluateSelect(state, valueMap); + if (unroll) { + if (result.second == nullptr) { + return EvaluationStateResult(nullptr, true, components); + } + if (state->name == newName) { + state->newState = new IR::ParserState(state->state->srcInfo, newName, + state->state->annotations, components, + result.second); + } else { + state->newState = + new IR::ParserState(state->state->srcInfo, newName, components, result.second); + } + } + return EvaluationStateResult(result.first, true, components); + } + + public: + bool hasOutOfboundState; + /// constructor + ParserSymbolicInterpreter(ParserStructure* structure, ReferenceMap* refMap, TypeMap* typeMap, + bool unroll, bool& wasError) : structure(structure), refMap(refMap), + typeMap(typeMap), synthesizedParser(nullptr), unroll(unroll), + wasError(wasError) { + CHECK_NULL(structure); CHECK_NULL(refMap); CHECK_NULL(typeMap); + factory = new SymbolicValueFactory(typeMap); + parser = structure->parser; + hasOutOfboundState = false; + } + + /// generate call OutOfBound + void addOutOfBound(ParserStateInfo* stateInfo, std::unordered_set& newStates, + bool checkBefore = true, IR::IndexedVector components = + IR::IndexedVector()) { + IR::ID newName = getNewName(stateInfo); + if (checkBefore && newStates.count(newName)) { + return; + } + hasOutOfboundState = true; + newStates.insert(newName); + stateInfo->newState = new IR::ParserState(newName, + components, new IR::PathExpression(new IR::Type_State(), + new IR::Path(outOfBoundsStateName, false))); + } + + /// running symbolic execution + ParserInfo* run() { + synthesizedParser = new ParserInfo(); + auto initMap = initializeVariables(); + if (initMap == nullptr) + // error during initializer evaluation + return synthesizedParser; + auto startInfo = newStateInfo(nullptr, structure->start->name.name, initMap, 0); + structure->callsIndexes.emplace(structure->start->name.name, 0); + startInfo->scenarioStates.insert(structure->start->name.name); + std::vector toRun; // worklist + toRun.push_back(startInfo); + std::set visited; + std::unordered_set newStates; + while (!toRun.empty()) { + auto stateInfo = toRun.back(); + toRun.pop_back(); + LOG1("Symbolic evaluation of " << stateChain(stateInfo)); + // checking visited state, loop state, and the reachable states with needed header stack + // operators. + if (visited.count(VisitedKey(stateInfo)) && + !stateInfo->scenarioStates.count(stateInfo->name) && + !structure->reachableHSUsage(stateInfo->state->name, stateInfo)) + continue; + auto iHSNames = structure->statesWithHeaderStacks.find(stateInfo->name); + if (iHSNames != structure->statesWithHeaderStacks.end()) + stateInfo->scenarioHS.insert(iHSNames->second.begin(), iHSNames->second.end()); + visited.insert(VisitedKey(stateInfo)); // add to visited map + stateInfo->scenarioStates.insert(stateInfo->name); // add to loops detection + bool infLoop = checkLoops(stateInfo); + if (infLoop) { + // Stop unrolling if it was an error. + if (wasError) { + IR::ID newName = getNewName(stateInfo); + if (newStates.count(newName) != 0) { + evaluateState(stateInfo, newStates); + } + wasError = false; + continue; + } + // don't evaluate successors anymore + // generate call OutOfBound + addOutOfBound(stateInfo, newStates); + continue; + } + IR::ID newName = getNewName(stateInfo); + bool notAdded = newStates.count(newName) == 0; + auto nextStates = evaluateState(stateInfo, newStates); + if (get<0>(nextStates) == nullptr) { + if (get<1>(nextStates) && stateInfo->predecessor && + newName.name !=stateInfo->predecessor->newState->name) { + // generate call OutOfBound + addOutOfBound(stateInfo, newStates, false, get<2>(nextStates)); + } else { + // save current state + if (notAdded) { + stateInfo->newState = stateInfo->state->clone(); + } + } + LOG1("No next states"); + continue; + } + toRun.insert(toRun.end(), get<0>(nextStates)->begin(), get<0>(nextStates)->end()); + } + + return synthesizedParser; + } +}; + +} // namespace ParserStructureImpl + +bool ParserStructure::analyze(ReferenceMap* refMap, TypeMap* typeMap, bool unroll, bool& wasError) { + ParserStructureImpl::ParserSymbolicInterpreter psi(this, refMap, typeMap, unroll, wasError); + result = psi.run(); + return psi.hasOutOfboundState; +} + +/// check reachability for usage of header stack +bool ParserStructure::reachableHSUsage(IR::ID id, const ParserStateInfo* state) const { + if (!state->scenarioHS.size()) + return false; + CHECK_NULL(callGraph); + const IR::IDeclaration* declaration = parser->states.getDeclaration(id.name); + BUG_CHECK(declaration && declaration->is(), "Invalid declaration %1%", id); + std::set reachableStates; + callGraph->reachable(declaration->to(), reachableStates); + std::set reachebleHSoperators; + for (auto i : reachableStates) { + auto iHSNames = statesWithHeaderStacks.find(i->name); + if (iHSNames != statesWithHeaderStacks.end()) + reachebleHSoperators.insert(iHSNames->second.begin(), iHSNames->second.end()); + } + std::set intersectionHSOperators; + std::set_intersection(state->scenarioHS.begin(), state->scenarioHS.end(), + reachebleHSoperators.begin(), reachebleHSoperators.end(), + std::inserter(intersectionHSOperators, + intersectionHSOperators.begin())); + return intersectionHSOperators.size() > 0; +} + +void ParserStructure::addStateHSUsage(const IR::ParserState* state, + const IR::Expression* expression) { + if (state == nullptr || expression == nullptr || !expression->type->is()) + return; + auto i = statesWithHeaderStacks.find(state->name.name); + if (i == statesWithHeaderStacks.end()) { + std::set s; + s.insert(expression->toString()); + statesWithHeaderStacks.emplace(state->name.name, s); + } else { + i->second.insert(expression->toString()); + } +} + + +} // namespace P4 diff --git a/midend/parserUnroll.h b/midend/parserUnroll.h index 77a2450ec1d..e1692095944 100644 --- a/midend/parserUnroll.h +++ b/midend/parserUnroll.h @@ -1,297 +1,297 @@ -/* -Copyright 2016 VMware, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -#ifndef _MIDEND_PARSERUNROLL_H_ -#define _MIDEND_PARSERUNROLL_H_ - -#include - -#include "ir/ir.h" -#include "frontends/common/resolveReferences/referenceMap.h" -#include "frontends/p4/callGraph.h" -#include "frontends/p4/typeChecking/typeChecker.h" -#include "frontends/p4/typeMap.h" -#include "frontends/p4/simplify.h" -#include "interpreter.h" - -namespace P4 { - -/// Name of out of bound state -const char outOfBoundsStateName[] = "stateOutOfBound"; - -////////////////////////////////////////////// -// The following are for a single parser - -// Represents a variable for storing indexes values for a header stack. -// -// This is a thin wrapper around a 'const IR::Member*' to (1) enforce invariants on which forms of -// Members can represent state variables and (2) enable the use of StackVariable as map keys. -// -// A Member can represent a StackVariable exactly when its qualifying variable -// (IR::Member::expr) either is a PathExpression or can represent a StackVariable. -class StackVariable { - friend class StackVariableHash; - public: - /// Determines whether @expr can represent a StateVariable. - static bool repOk(const IR::Expression* expr); - - // Implements comparisons so that StateVariables can be used as map keys. - bool operator==(const StackVariable& other) const; - - private: - const IR::Expression* variable; - - public: - /// Implicitly converts IR::Expression* to a StackVariable. - StackVariable(const IR::Expression* expr); // NOLINT(runtime/explicit) -}; - -/// Class with hash function for @a StackVariable. -class StackVariableHash { - public: - size_t operator()(const StackVariable& var) const; -}; - -typedef std::unordered_map StackVariableMap; -typedef std::unordered_map StackVariableIndexMap; - -/// Information produced for a parser state by the symbolic evaluator -struct ParserStateInfo { - friend class ParserStateRewriter; - cstring name; // new state name - const IR::P4Parser* parser; - const IR::ParserState* state; // original state this is produced from - const ParserStateInfo* predecessor; // how we got here in the symbolic evaluation - ValueMap* before; - ValueMap* after; - IR::ParserState* newState; // pointer to a new state - size_t currentIndex; - StackVariableMap statesIndexes; // global map in state indexes - // set of parsers' states names with are in current path. - std::unordered_set scenarioStates; - std::unordered_set scenarioHS; // scenario header stack's operations - StackVariableIndexMap substitutedIndexes; // values of the evaluated indexes - ParserStateInfo(cstring name, const IR::P4Parser* parser, const IR::ParserState* state, - const ParserStateInfo* predecessor, ValueMap* before, size_t index) : - name(name), parser(parser), state(state), predecessor(predecessor), - before(before), after(nullptr), newState(nullptr), currentIndex(index) { - CHECK_NULL(parser); CHECK_NULL(state); CHECK_NULL(before); - if (predecessor) { - statesIndexes = predecessor->statesIndexes; - scenarioHS = predecessor->scenarioHS; - substitutedIndexes = predecessor->substitutedIndexes; - } - } -}; - -/// Information produced for a parser by the symbolic evaluator -class ParserInfo { - friend class RewriteAllParsers; - // for each original state a vector of states produced by unrolling - std::map*> states; - public: - std::vector* get(cstring origState) { - std::vector *vec; - auto it = states.find(origState); - if (it == states.end()) { - vec = new std::vector; - states.emplace(origState, vec); - } else { - vec = it->second; - } - return vec; - } - void add(ParserStateInfo* si) { - cstring origState = si->state->name.name; - auto vec = get(origState); - vec->push_back(si); - } - std::map*>& getStates() { - return states; - } -}; - -typedef CallGraph StateCallGraph; - -/// Information about a parser in the input program -class ParserStructure { - friend class ParserStateRewriter; - friend class ParserSymbolicInterpreter; - friend class AnalyzeParser; - std::map stateMap; - - public: - const IR::P4Parser* parser; - const IR::ParserState* start; - const ParserInfo* result; - StateCallGraph* callGraph; - std::map > statesWithHeaderStacks; - std::map callsIndexes; // map for curent calls of state insite current one - void setParser(const IR::P4Parser* parser) { - CHECK_NULL(parser); - callGraph = new StateCallGraph(parser->name); - this->parser = parser; - start = nullptr; - } - void addState(const IR::ParserState* state) - { stateMap.emplace(state->name, state); } - const IR::ParserState* get(cstring state) const - { return ::get(stateMap, state); } - void calls(const IR::ParserState* caller, const IR::ParserState* callee) - { callGraph->calls(caller, callee); } - - bool analyze(ReferenceMap* refMap, TypeMap* typeMap, bool unroll, bool& wasError); - /// check reachability for usage of header stack - bool reachableHSUsage(IR::ID id, const ParserStateInfo* state) const; - - protected: - /// evaluates rechable states with HS operations for each path. - void evaluateReachability(); - /// add HS name which is used in a current state. - void addStateHSUsage(const IR::ParserState* state, const IR::Expression* expression); -}; - -class AnalyzeParser : public Inspector { - const ReferenceMap* refMap; - ParserStructure* current; - const IR::ParserState* currentState; - public: - AnalyzeParser(const ReferenceMap* refMap, ParserStructure* current) : - refMap(refMap), current(current), currentState(nullptr) { - CHECK_NULL(refMap); CHECK_NULL(current); setName("AnalyzeParser"); - visitDagOnce = false; - } - - bool preorder(const IR::P4Parser* parser) override { - LOG2("Scanning " << parser); - current->setParser(parser); - return true; - } - bool preorder(const IR::ParserState* state) override; - void postorder(const IR::ParserState* state) override; - void postorder(const IR::ArrayIndex* array) override; - void postorder(const IR::Member* member) override; - void postorder(const IR::PathExpression* expression) override; -}; - -// Applied to a P4Parser object. -class ParserRewriter : public PassManager { - ParserStructure current; - friend class RewriteAllParsers; - public: - bool hasOutOfboundState; - bool wasError; - ParserRewriter(ReferenceMap* refMap, - TypeMap* typeMap, bool unroll) { - CHECK_NULL(refMap); CHECK_NULL(typeMap); - wasError = false; - setName("ParserRewriter"); - addPasses({ - new AnalyzeParser(refMap, ¤t), - [this, refMap, typeMap, unroll](void) { - hasOutOfboundState = current.analyze(refMap, typeMap, unroll, wasError); }, - }); - } -}; - -/////////////////////////////////////////////////////// -// The following are applied to the entire program - -class RewriteAllParsers : public Transform { - ReferenceMap* refMap; - TypeMap* typeMap; - bool unroll; - - public: - RewriteAllParsers(ReferenceMap* refMap, TypeMap* typeMap, bool unroll) : - refMap(refMap), typeMap(typeMap), unroll(unroll) { - CHECK_NULL(refMap); CHECK_NULL(typeMap); setName("RewriteAllParsers"); - } - - // start generation of a code - const IR::Node* postorder(IR::P4Parser* parser) override { - // making rewriting - auto rewriter = new ParserRewriter(refMap, typeMap, unroll); - rewriter->setCalledBy(this); - parser->apply(*rewriter); - if (rewriter->wasError) { - return parser; - } - /// make a new parser - BUG_CHECK(rewriter->current.result, - "No result was found after unrolling of the parser loop"); - IR::P4Parser* newParser = parser->clone(); - IR::IndexedVector states = newParser->states; - newParser->states.clear(); - if (rewriter->hasOutOfboundState) { - // generating state with verify(false, error.StackOutOfBounds) - IR::Vector* arguments = new IR::Vector(); - arguments->push_back( - new IR::Argument(new IR::BoolLiteral(IR::Type::Boolean::get(), false))); - arguments->push_back(new IR::Argument(new IR::Member( - new IR::TypeNameExpression(new IR::Type_Name(IR::ID("error"))), - IR::ID("StackOutOfBounds")))); - IR::IndexedVector components; - IR::IndexedVector parameters; - parameters.push_back( - new IR::Parameter(IR::ID("check"), IR::Direction::In, IR::Type::Boolean::get())); - parameters.push_back(new IR::Parameter(IR::ID("toSignal"), IR::Direction::In, - new IR::Type_Name(IR::ID("error")))); - components.push_back(new IR::MethodCallStatement(new IR::MethodCallExpression( - IR::Type::Void::get(), - new IR::PathExpression( - new IR::Type_Method(IR::Type::Void::get(), new IR::ParameterList(parameters), - "*method"), - new IR::Path(IR::ID("verify"))), - arguments))); - auto* outOfBoundsState = new IR::ParserState( - IR::ID(outOfBoundsStateName), components, - new IR::PathExpression(new IR::Type_State(), - new IR::Path(IR::ParserState::reject, false))); - newParser->states.push_back(outOfBoundsState); - } - for (auto& i : rewriter->current.result->states) { - for (auto& j : *i.second) - if (j->newState) { - if (rewriter->hasOutOfboundState && - j->newState->name.name == "stateOutOfBound") { - continue; - } - newParser->states.push_back(j->newState); - } - } - // adding accept/reject - newParser->states.push_back(new IR::ParserState(IR::ParserState::accept, nullptr)); - newParser->states.push_back(new IR::ParserState(IR::ParserState::reject, nullptr)); - return newParser; - } -}; - -class ParsersUnroll : public PassManager { - public: - ParsersUnroll(bool unroll, ReferenceMap* refMap, TypeMap* typeMap) { - // remove block statements - passes.push_back(new SimplifyControlFlow(refMap, typeMap)); - passes.push_back(new TypeChecking(refMap, typeMap)); - passes.push_back(new RewriteAllParsers(refMap, typeMap, unroll)); - setName("ParsersUnroll"); - } -}; - -} // namespace P4 - -#endif /* _MIDEND_PARSERUNROLL_H_ */ +/* +Copyright 2016 VMware, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#ifndef _MIDEND_PARSERUNROLL_H_ +#define _MIDEND_PARSERUNROLL_H_ + +#include + +#include "ir/ir.h" +#include "frontends/common/resolveReferences/referenceMap.h" +#include "frontends/p4/callGraph.h" +#include "frontends/p4/typeChecking/typeChecker.h" +#include "frontends/p4/typeMap.h" +#include "frontends/p4/simplify.h" +#include "interpreter.h" + +namespace P4 { + +/// Name of out of bound state +const char outOfBoundsStateName[] = "stateOutOfBound"; + +////////////////////////////////////////////// +// The following are for a single parser + +// Represents a variable for storing indexes values for a header stack. +// +// This is a thin wrapper around a 'const IR::Member*' to (1) enforce invariants on which forms of +// Members can represent state variables and (2) enable the use of StackVariable as map keys. +// +// A Member can represent a StackVariable exactly when its qualifying variable +// (IR::Member::expr) either is a PathExpression or can represent a StackVariable. +class StackVariable { + friend class StackVariableHash; + public: + /// Determines whether @expr can represent a StateVariable. + static bool repOk(const IR::Expression* expr); + + // Implements comparisons so that StateVariables can be used as map keys. + bool operator==(const StackVariable& other) const; + + private: + const IR::Expression* variable; + + public: + /// Implicitly converts IR::Expression* to a StackVariable. + StackVariable(const IR::Expression* expr); // NOLINT(runtime/explicit) +}; + +/// Class with hash function for @a StackVariable. +class StackVariableHash { + public: + size_t operator()(const StackVariable& var) const; +}; + +typedef std::unordered_map StackVariableMap; +typedef std::unordered_map StackVariableIndexMap; + +/// Information produced for a parser state by the symbolic evaluator +struct ParserStateInfo { + friend class ParserStateRewriter; + cstring name; // new state name + const IR::P4Parser* parser; + const IR::ParserState* state; // original state this is produced from + const ParserStateInfo* predecessor; // how we got here in the symbolic evaluation + ValueMap* before; + ValueMap* after; + IR::ParserState* newState; // pointer to a new state + size_t currentIndex; + StackVariableMap statesIndexes; // global map in state indexes + // set of parsers' states names with are in current path. + std::unordered_set scenarioStates; + std::unordered_set scenarioHS; // scenario header stack's operations + StackVariableIndexMap substitutedIndexes; // values of the evaluated indexes + ParserStateInfo(cstring name, const IR::P4Parser* parser, const IR::ParserState* state, + const ParserStateInfo* predecessor, ValueMap* before, size_t index) : + name(name), parser(parser), state(state), predecessor(predecessor), + before(before), after(nullptr), newState(nullptr), currentIndex(index) { + CHECK_NULL(parser); CHECK_NULL(state); CHECK_NULL(before); + if (predecessor) { + statesIndexes = predecessor->statesIndexes; + scenarioHS = predecessor->scenarioHS; + substitutedIndexes = predecessor->substitutedIndexes; + } + } +}; + +/// Information produced for a parser by the symbolic evaluator +class ParserInfo { + friend class RewriteAllParsers; + // for each original state a vector of states produced by unrolling + std::map*> states; + public: + std::vector* get(cstring origState) { + std::vector *vec; + auto it = states.find(origState); + if (it == states.end()) { + vec = new std::vector; + states.emplace(origState, vec); + } else { + vec = it->second; + } + return vec; + } + void add(ParserStateInfo* si) { + cstring origState = si->state->name.name; + auto vec = get(origState); + vec->push_back(si); + } + std::map*>& getStates() { + return states; + } +}; + +typedef CallGraph StateCallGraph; + +/// Information about a parser in the input program +class ParserStructure { + friend class ParserStateRewriter; + friend class ParserSymbolicInterpreter; + friend class AnalyzeParser; + std::map stateMap; + + public: + const IR::P4Parser* parser; + const IR::ParserState* start; + const ParserInfo* result; + StateCallGraph* callGraph; + std::map > statesWithHeaderStacks; + std::map callsIndexes; // map for curent calls of state insite current one + void setParser(const IR::P4Parser* parser) { + CHECK_NULL(parser); + callGraph = new StateCallGraph(parser->name); + this->parser = parser; + start = nullptr; + } + void addState(const IR::ParserState* state) + { stateMap.emplace(state->name, state); } + const IR::ParserState* get(cstring state) const + { return ::get(stateMap, state); } + void calls(const IR::ParserState* caller, const IR::ParserState* callee) + { callGraph->calls(caller, callee); } + + bool analyze(ReferenceMap* refMap, TypeMap* typeMap, bool unroll, bool& wasError); + /// check reachability for usage of header stack + bool reachableHSUsage(IR::ID id, const ParserStateInfo* state) const; + + protected: + /// evaluates rechable states with HS operations for each path. + void evaluateReachability(); + /// add HS name which is used in a current state. + void addStateHSUsage(const IR::ParserState* state, const IR::Expression* expression); +}; + +class AnalyzeParser : public Inspector { + const ReferenceMap* refMap; + ParserStructure* current; + const IR::ParserState* currentState; + public: + AnalyzeParser(const ReferenceMap* refMap, ParserStructure* current) : + refMap(refMap), current(current), currentState(nullptr) { + CHECK_NULL(refMap); CHECK_NULL(current); setName("AnalyzeParser"); + visitDagOnce = false; + } + + bool preorder(const IR::P4Parser* parser) override { + LOG2("Scanning " << parser); + current->setParser(parser); + return true; + } + bool preorder(const IR::ParserState* state) override; + void postorder(const IR::ParserState* state) override; + void postorder(const IR::ArrayIndex* array) override; + void postorder(const IR::Member* member) override; + void postorder(const IR::PathExpression* expression) override; +}; + +// Applied to a P4Parser object. +class ParserRewriter : public PassManager { + ParserStructure current; + friend class RewriteAllParsers; + public: + bool hasOutOfboundState; + bool wasError; + ParserRewriter(ReferenceMap* refMap, + TypeMap* typeMap, bool unroll) { + CHECK_NULL(refMap); CHECK_NULL(typeMap); + wasError = false; + setName("ParserRewriter"); + addPasses({ + new AnalyzeParser(refMap, ¤t), + [this, refMap, typeMap, unroll](void) { + hasOutOfboundState = current.analyze(refMap, typeMap, unroll, wasError); }, + }); + } +}; + +/////////////////////////////////////////////////////// +// The following are applied to the entire program + +class RewriteAllParsers : public Transform { + ReferenceMap* refMap; + TypeMap* typeMap; + bool unroll; + + public: + RewriteAllParsers(ReferenceMap* refMap, TypeMap* typeMap, bool unroll) : + refMap(refMap), typeMap(typeMap), unroll(unroll) { + CHECK_NULL(refMap); CHECK_NULL(typeMap); setName("RewriteAllParsers"); + } + + // start generation of a code + const IR::Node* postorder(IR::P4Parser* parser) override { + // making rewriting + auto rewriter = new ParserRewriter(refMap, typeMap, unroll); + rewriter->setCalledBy(this); + parser->apply(*rewriter); + if (rewriter->wasError) { + return parser; + } + /// make a new parser + BUG_CHECK(rewriter->current.result, + "No result was found after unrolling of the parser loop"); + IR::P4Parser* newParser = parser->clone(); + IR::IndexedVector states = newParser->states; + newParser->states.clear(); + if (rewriter->hasOutOfboundState) { + // generating state with verify(false, error.StackOutOfBounds) + IR::Vector* arguments = new IR::Vector(); + arguments->push_back( + new IR::Argument(new IR::BoolLiteral(IR::Type::Boolean::get(), false))); + arguments->push_back(new IR::Argument(new IR::Member( + new IR::TypeNameExpression(new IR::Type_Name(IR::ID("error"))), + IR::ID("StackOutOfBounds")))); + IR::IndexedVector components; + IR::IndexedVector parameters; + parameters.push_back( + new IR::Parameter(IR::ID("check"), IR::Direction::In, IR::Type::Boolean::get())); + parameters.push_back(new IR::Parameter(IR::ID("toSignal"), IR::Direction::In, + new IR::Type_Name(IR::ID("error")))); + components.push_back(new IR::MethodCallStatement(new IR::MethodCallExpression( + IR::Type::Void::get(), + new IR::PathExpression( + new IR::Type_Method(IR::Type::Void::get(), new IR::ParameterList(parameters), + "*method"), + new IR::Path(IR::ID("verify"))), + arguments))); + auto* outOfBoundsState = new IR::ParserState( + IR::ID(outOfBoundsStateName), components, + new IR::PathExpression(new IR::Type_State(), + new IR::Path(IR::ParserState::reject, false))); + newParser->states.push_back(outOfBoundsState); + } + for (auto& i : rewriter->current.result->states) { + for (auto& j : *i.second) + if (j->newState) { + if (rewriter->hasOutOfboundState && + j->newState->name.name == "stateOutOfBound") { + continue; + } + newParser->states.push_back(j->newState); + } + } + // adding accept/reject + newParser->states.push_back(new IR::ParserState(IR::ParserState::accept, nullptr)); + newParser->states.push_back(new IR::ParserState(IR::ParserState::reject, nullptr)); + return newParser; + } +}; + +class ParsersUnroll : public PassManager { + public: + ParsersUnroll(bool unroll, ReferenceMap* refMap, TypeMap* typeMap) { + // remove block statements + passes.push_back(new SimplifyControlFlow(refMap, typeMap)); + passes.push_back(new TypeChecking(refMap, typeMap)); + passes.push_back(new RewriteAllParsers(refMap, typeMap, unroll)); + setName("ParsersUnroll"); + } +}; + +} // namespace P4 + +#endif /* _MIDEND_PARSERUNROLL_H_ */ From 0cd4f21a520db7be29dab42e41b469992446bccc Mon Sep 17 00:00:00 2001 From: VolodymyrPeschanenkoIntel Date: Mon, 21 Nov 2022 16:45:19 +0200 Subject: [PATCH 12/12] update --- midend/parserUnroll.cpp | 36 +++++++++++++++++++++--------------- midend/parserUnroll.h | 10 +++++----- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/midend/parserUnroll.cpp b/midend/parserUnroll.cpp index 88b2a432003..1b0621e84b3 100644 --- a/midend/parserUnroll.cpp +++ b/midend/parserUnroll.cpp @@ -292,12 +292,14 @@ class ParserStateRewriter : public Transform { if (isTerminalState(id)) return id; size_t index = 0; cstring name = id.name; - if (parserStructure->callsIndexes.count(id.name) && (state->scenarioStates.count(id.name) - || parserStructure->reachableHSUsage(id, state))) { + if (parserStructure->callsIndexes.count(id.name) && + (state->scenarioStates.count(id.name) || + parserStructure->reachableHSUsage(id, state))) { // or self called with no extraction... // in this case we need to use the same name as it was in previous call - if (was_called(name, id)) + if (was_called(name, id)) { return id; + } if (calledWithNoChanges(id, state)) { index = 0; if (parserStructure->callsIndexes.count(id.name)) { @@ -696,8 +698,8 @@ class ParserSymbolicInterpreter { return IR::ID(state->state->name + std::to_string(state->currentIndex)); } - using EvaluationStateResult = std::tuple*, bool, - IR::IndexedVector>; + using EvaluationStateResult = + std::tuple*, bool, IR::IndexedVector>; /// Generates new state with the help of symbolic execution. /// If corresponded state was generated previously then it returns @a nullptr and false. @@ -710,16 +712,19 @@ class ParserSymbolicInterpreter { IR::ID newName; if (unroll) { newName = getNewName(state); - if (newStates.count(newName)) + if (newStates.count(newName)) { return EvaluationStateResult(nullptr, false, components); + } newStates.insert(newName); } for (auto s : state->state->components) { auto* newComponent = executeStatement(state, s, valueMap); - if (!newComponent) + if (!newComponent) { return EvaluationStateResult(nullptr, true, components); - if (unroll) + } + if (unroll) { components.push_back(newComponent); + } } state->after = valueMap; auto result = evaluateSelect(state, valueMap); @@ -758,19 +763,20 @@ class ParserSymbolicInterpreter { hasOutOfboundState = false; } + using StatOrDeclVector = IR::IndexedVector; + /// generate call OutOfBound void addOutOfBound(ParserStateInfo* stateInfo, std::unordered_set& newStates, - bool checkBefore = true, IR::IndexedVector components = - IR::IndexedVector()) { + bool checkBefore = true, StatOrDeclVector components = StatOrDeclVector()) { IR::ID newName = getNewName(stateInfo); if (checkBefore && newStates.count(newName)) { return; } hasOutOfboundState = true; newStates.insert(newName); - stateInfo->newState = new IR::ParserState(newName, - components, new IR::PathExpression(new IR::Type_State(), - new IR::Path(outOfBoundsStateName, false))); + auto* pathExpr = + new IR::PathExpression(new IR::Type_State(), new IR::Path(outOfBoundsStateName, false)); + stateInfo->newState = new IR::ParserState(newName, components, pathExpr); } /// running symbolic execution @@ -810,7 +816,7 @@ class ParserSymbolicInterpreter { if (newStates.count(newName) != 0) { evaluateState(stateInfo, newStates); } - wasError = false; + wasError = false; continue; } // don't evaluate successors anymore @@ -823,7 +829,7 @@ class ParserSymbolicInterpreter { auto nextStates = evaluateState(stateInfo, newStates); if (get<0>(nextStates) == nullptr) { if (get<1>(nextStates) && stateInfo->predecessor && - newName.name !=stateInfo->predecessor->newState->name) { + newName.name != stateInfo->predecessor->newState->name) { // generate call OutOfBound addOutOfBound(stateInfo, newStates, false, get<2>(nextStates)); } else { diff --git a/midend/parserUnroll.h b/midend/parserUnroll.h index 42b99a496ca..3997328e213 100644 --- a/midend/parserUnroll.h +++ b/midend/parserUnroll.h @@ -67,8 +67,8 @@ class StackVariableHash { }; typedef std::unordered_map StackVariableMap; -typedef std::unordered_map StackVariableIndexMap; +typedef std::unordered_map + StackVariableIndexMap; /// Information produced for a parser state by the symbolic evaluator struct ParserStateInfo { @@ -83,9 +83,9 @@ struct ParserStateInfo { size_t currentIndex; StackVariableMap statesIndexes; // global map in state indexes // set of parsers' states names with are in current path. - std::unordered_set scenarioStates; - std::unordered_set scenarioHS; // scenario header stack's operations - StackVariableIndexMap substitutedIndexes; // values of the evaluated indexes + std::unordered_set scenarioStates; + std::unordered_set scenarioHS; // scenario header stack's operations + StackVariableIndexMap substitutedIndexes; // values of the evaluated indexes ParserStateInfo(cstring name, const IR::P4Parser* parser, const IR::ParserState* state, const ParserStateInfo* predecessor, ValueMap* before, size_t index) : name(name),