Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add duplicate action control plane name check pass #4951

Closed
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e429c8a
Add pass to give error if duplicate hierarchical names are present
jafingerhut Oct 7, 2024
39538bf
Add new source files to CMakeLists.txt
jafingerhut Oct 7, 2024
98940fb
Skip new checks when compiling certain source files including: + Sour…
jafingerhut Oct 8, 2024
a2792ce
Skip new checks for P4-14 source files in run-bmv2-test.py
jafingerhut Oct 8, 2024
a553fe1
Fix lint error
jafingerhut Oct 8, 2024
26f0501
Fix lint problems.
jafingerhut Oct 8, 2024
da456fe
Do not give errors for variable declarations with identical names
jafingerhut Oct 8, 2024
b683571
Limit search for duplicate names to actions, tables, or "other objects"
jafingerhut Oct 8, 2024
ad40fe9
Update test programs that had errors in their name annotations
jafingerhut Oct 9, 2024
91166c8
Update expected output files for modified test programs
jafingerhut Oct 9, 2024
9acb7e0
Move new test program with error into p4_16_errors directory
jafingerhut Oct 9, 2024
b133b28
Add expected output files for new test programs
jafingerhut Oct 9, 2024
76bf24f
More fine-tuning to enable tests to pass
jafingerhut Oct 9, 2024
f7ec2fe
Fix lint error
jafingerhut Oct 9, 2024
1cc8224
Fix lint error
jafingerhut Oct 9, 2024
4b03eb5
Merge branch 'master' into add-duplicate-hierarchical-name-check-pass
jafingerhut Oct 9, 2024
93daba5
Address some review comments
jafingerhut Oct 9, 2024
66a0a0a
Rewording documentation of new pass for clarity.
jafingerhut Oct 9, 2024
083db34
Merge remote-tracking branch 'upstream/main' into add-duplicate-hiera…
jafingerhut Oct 12, 2024
99beb30
Enable the new pass only if P4Runtime control plane API gen is enabled
jafingerhut Oct 20, 2024
2b7c58a
Merge branch 'add-duplicate-hierarchical-name-check-pass' of github.c…
jafingerhut Oct 20, 2024
54007df
Merge remote-tracking branch 'upstream/main' into add-duplicate-hiera…
jafingerhut Oct 20, 2024
d89f214
Undo changes to Python test scripts
jafingerhut Oct 20, 2024
7424db3
Undo changes to gtestp4c source
jafingerhut Oct 20, 2024
55ad18f
Merge remote-tracking branch 'upstream/main' into add-duplicate-hiera…
jafingerhut Oct 20, 2024
799448a
Use abseil StrCat and StrAppend instead of loop
jafingerhut Oct 21, 2024
377f41a
Fix formatting with clang-format
jafingerhut Oct 21, 2024
d265e1c
Also catch duplicates involving top-level actions without @name annot…
jafingerhut Oct 21, 2024
606300e
Merge remote-tracking branch 'upstream/main' into add-duplicate-hiera…
jafingerhut Oct 21, 2024
6bcb614
Add new test program to exercise latest code changes
jafingerhut Oct 21, 2024
55da2c3
Change pass name to reflect only checking action control plane names
jafingerhut Oct 21, 2024
d67df83
Fix lint warning, and add one more successful test program
jafingerhut Oct 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion control-plane/p4RuntimeSerializer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1584,7 +1584,7 @@ void P4RuntimeSerializer::serializeP4RuntimeIfRequired(const IR::P4Program *prog
std::vector<cstring> files;
std::vector<P4::P4RuntimeFormat> formats;

// only generate P4Info is required by use-provided options
// only generate P4Info is required by user-provided options
if (options.p4RuntimeFile.isNullOrEmpty() && options.p4RuntimeFiles.isNullOrEmpty() &&
options.p4RuntimeEntriesFile.isNullOrEmpty() &&
options.p4RuntimeEntriesFiles.isNullOrEmpty()) {
Expand Down
2 changes: 2 additions & 0 deletions frontends/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ set (P4_FRONTEND_SRCS
p4/frontend.cpp
p4/functionsInlining.cpp
p4/hierarchicalNames.cpp
p4/duplicateHierarchicalNameCheck.cpp
p4/inlining.cpp
p4/localizeActions.cpp
p4/methodInstance.cpp
Expand Down Expand Up @@ -110,6 +111,7 @@ set (P4_FRONTEND_HDRS
p4/frontend.h
p4/functionsInlining.h
p4/hierarchicalNames.h
p4/duplicateHierarchicalNameCheck.h
p4/inlining.h
p4/localizeActions.h
p4/methodInstance.h
Expand Down
81 changes: 81 additions & 0 deletions frontends/p4/duplicateHierarchicalNameCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
Copyright 2024 Cisco Systems, 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.
*/

#include "duplicateHierarchicalNameCheck.h"

#include "lib/log.h"

namespace P4 {

cstring DuplicateHierarchicalNameCheck::getName(const IR::IDeclaration *decl) {
return decl->getName();
}

const IR::Node *DuplicateHierarchicalNameCheck::postorder(IR::Annotation *annotation) {
if (annotation->name != IR::Annotation::nameAnnotation) return annotation;

cstring name = annotation->getName();
if (!name.startsWith(".")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably it would be better to use absl::StrJoin here, something like this:
absl::StrCat(absl::StrJoin(stack, "."), name.string_view()). Though this might break up as we'd need to explicitly cast from cstring to string_view, then we'd need to provide lambda to StrJoin.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to address this comment, but my C++-fu is pretty weak these days, even with your code snippet and other tips. If you or someone else has the time to provide a specific working replacement, I am happy to use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asl As of commit 25 on this PR, I have tried to address 2 of your 3 comments, and I think it is ready for a fresh review. If you have more specific suggestions for code changes on this commentt, which I have not addressed as mentioned in my comment above, please let me know.

Copy link
Contributor

@asl asl Oct 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, does the following work?

name = absl::StrCat(absl::StrJoin(stack, ".", [](std::string *out, cstring s) {
                                 absl::StrAppend(out, s.string_view());
                             }), name.string_view())

Yes, we have not finished the cstring-related implicit conversion saga (I need to find another continuous chunk of time to convert everything...), so we cannot make cstring implicitly converted to string_view (yet), so these .string_view() calls are sadly necessary.

After the conversion it would be much nicer:

name = absl::StrCat(absl::StrJoin(stack, "."), name);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in commit 26

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jafingerhut After #4971 I believe we can just do name = absl::StrCat(absl::StrJoin(stack, "."), name);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to keep an eye out for when that PR is merged in, but feel free to ping me when it is, in case I miss it.

if (!stack.empty()) {
name = absl::StrCat(absl::StrJoin(stack, ".",
[](std::string *out, cstring s) {
absl::StrAppend(out, s.string_view());
}),
".", name.string_view());
}
}
// The node the annotation belongs to
CHECK_NULL(getContext()->parent);
auto *annotatedNode = getContext()->parent->node;
CHECK_NULL(annotatedNode);
// variable and struct declarations are fine if they have identical
// name annotations, and such name annotations can be synthesized by
// p4c before this pass. Ignore them.
if (annotatedNode->is<IR::Declaration_Variable>() || annotatedNode->is<IR::Type_Struct>()) {
return annotation;
}
bool foundDuplicate = false;
auto *otherNode = annotatedNode;
if (annotatedNode->is<IR::P4Table>()) {
if (annotatedTables.count(name)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could probably save one map lookup here, e.g.:

auto [it, inserted] = annotatedTables.insert(name, annotatedNode);
if (!inserted) {
  foundDuplicate = true;
  otherNode = *it;
}

Same for code below.

Probable the code could be deduplicated as well? Something like this:

auto checkForDuplicates = [&foundDuplicate, &otherNode](auto &namesToCheck, const IR::Node *annotatedNode) {
  auto [it, inserted] = namesToCheck.insert(name, annotatedNode);
  if (!inserted) {
    foundDuplicate = true;
    otherNode = *it;
  }
}

And then just call this lambda in each of this is checks:

if (annotatedNode->is<IR::P4Table>() {
  checkForDuplicates(annotatedTables, annotatedNode);
} else () ...

foundDuplicate = true;
otherNode = annotatedTables[name];
} else {
annotatedTables[name] = annotatedNode;
}
} else if (annotatedNode->is<IR::P4Action>()) {
if (annotatedActions.count(name)) {
foundDuplicate = true;
otherNode = annotatedActions[name];
} else {
annotatedActions[name] = annotatedNode;
}
} else {
if (annotatedOthers.count(name)) {
foundDuplicate = true;
otherNode = annotatedOthers[name];
} else {
annotatedOthers[name] = annotatedNode;
}
}
if (foundDuplicate) {
error(ErrorType::ERR_DUPLICATE, "%1%: conflicting control plane name: %2%", annotatedNode,
otherNode);
}
return annotation;
}

} // namespace P4
92 changes: 92 additions & 0 deletions frontends/p4/duplicateHierarchicalNameCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
Copyright 2024 Cisco Systems, 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 FRONTENDS_P4_DUPLICATEHIERARCHICALNAMECHECK_H_
#define FRONTENDS_P4_DUPLICATEHIERARCHICALNAMECHECK_H_

#include "ir/ir.h"
#include "ir/visitor.h"

namespace P4 {

/**
* This pass does not change anything in the IR. It only gives an
* error if it finds two objects with the same hierarchical name. I
* am not certain, but it might be that at this point in the front
* end, the only way such a duplicate can happen is due to @name
* annotations. @name annotations are definitely taken into account
* in this duplicate check.
*
* See also the pass HierarchicalNames, on which the implementation of
* this pass was based.
*
* This pass should be run before pass LocalizeAllActions, because
* LocalizeAllActions can create actions with duplicate names, by
* design, that were not created by the P4 developer. We should not
* issue an error if that pass creates duplicate hierarchical names.
*/
class DuplicateHierarchicalNameCheck : public Transform {
std::vector<cstring> stack;
/// Used for detection of conflicting control plane names among actions.
string_map<const IR::Node *> annotatedActions;
/// Used for detection of conflicting control plane names among tables.
string_map<const IR::Node *> annotatedTables;
/// Used for detection of conflicting control plane names among
/// objects other than actions and tables.
string_map<const IR::Node *> annotatedOthers;

public:
cstring getName(const IR::IDeclaration *decl);

DuplicateHierarchicalNameCheck() {
setName("DuplicateHierarchicalNameCheck");
visitDagOnce = false;
}
const IR::Node *preorder(IR::P4Parser *parser) override {
stack.push_back(getName(parser));
return parser;
}
const IR::Node *postorder(IR::P4Parser *parser) override {
stack.pop_back();
return parser;
}

const IR::Node *preorder(IR::P4Control *control) override {
stack.push_back(getName(control));
return control;
}
const IR::Node *postorder(IR::P4Control *control) override {
stack.pop_back();
return control;
}

const IR::Node *preorder(IR::P4Table *table) override {
visit(table->annotations);
prune();
return table;
}

const IR::Node *postorder(IR::Annotation *annotation) override;
// Do not change name annotations on parameters
const IR::Node *preorder(IR::Parameter *parameter) override {
prune();
return parameter;
}
};

} // namespace P4

#endif /* FRONTENDS_P4_DUPLICATEHIERARCHICALNAMECHECK_H_ */
10 changes: 10 additions & 0 deletions frontends/p4/frontend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ limitations under the License.
#include "deprecated.h"
#include "directCalls.h"
#include "dontcareArgs.h"
#include "duplicateHierarchicalNameCheck.h"
#include "entryPriorities.h"
#include "evaluator/evaluator.h"
#include "frontends/common/constantFolding.h"
Expand Down Expand Up @@ -234,6 +235,15 @@ const IR::P4Program *FrontEnd::run(const CompilerOptions &options, const IR::P4P
if (policy->optimize(options)) {
passes.addPasses({
new Inline(&typeMap, *policy, options.optimizeParserInlining),
});
}
if (policy->controlPlaneAPIGenEnabled(options)) {
passes.addPasses({
new DuplicateHierarchicalNameCheck(),
});
}
if (policy->optimize(options)) {
passes.addPasses({
new InlineActions(&typeMap, *policy),
new LocalizeAllActions(*policy),
new UniqueNames(),
Expand Down
12 changes: 12 additions & 0 deletions frontends/p4/frontend.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ class FrontEndPolicy : public RemoveUnusedPolicy {
return options.optimizationLevel > 0;
}

/// Indicates whether control plane API generation is enabled.
/// @returns default to false unless a command line option was
/// given explicitly enabling control plane API generation.
virtual bool controlPlaneAPIGenEnabled(const CompilerOptions &options) const {
if (options.p4RuntimeFile.isNullOrEmpty() && options.p4RuntimeFiles.isNullOrEmpty() &&
options.p4RuntimeEntriesFile.isNullOrEmpty() &&
options.p4RuntimeEntriesFiles.isNullOrEmpty()) {
return false;
}
return true;
}

/// Get policy for the constant folding pass. @see ConstantFoldingPolicy
/// @returns Defaults to nullptr, which causes constant folding to use the default policy, which
/// does not modify the pass defaults in any way.
Expand Down
84 changes: 84 additions & 0 deletions testdata/p4_16_errors/issue1949-bmv2.p4
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
Copyright 2019 Cisco Systems, 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.
*/

#include <core.p4>
#include <v1model.p4>

header h1_t {
bit<8> f1;
bit<8> f2;
}

struct headers_t {
h1_t h1;
}

struct metadata_t {
}

parser parserImpl(packet_in packet,
out headers_t hdr,
inout metadata_t meta,
inout standard_metadata_t stdmeta)
{
state start {
packet.extract(hdr.h1);
transition accept;
}
}

control ingressImpl(inout headers_t hdr,
inout metadata_t meta,
inout standard_metadata_t stdmeta)
{
@name(".foo") action act1() {
hdr.h1.f1 = hdr.h1.f1 >> 2;
}
@name(".foo") action act2() {
hdr.h1.f1 = hdr.h1.f1 << 3;
}
table t1 {
key = { hdr.h1.f1 : exact; }
actions = { act1; act2; NoAction; }
const default_action = NoAction;
}
apply {
t1.apply();
}
}

control verifyChecksum(inout headers_t hdr, inout metadata_t meta) {
apply {
}
}

control egressImpl(inout headers_t hdr, inout metadata_t meta, inout standard_metadata_t stdmeta) {
apply {
}
}

control updateChecksum(inout headers_t hdr, inout metadata_t meta) {
apply {
}
}

control deparserImpl(packet_out packet, in headers_t hdr) {
apply {
}
}

V1Switch<headers_t, metadata_t>(parserImpl(), verifyChecksum(), ingressImpl(), egressImpl(), updateChecksum(), deparserImpl()) main;

Loading
Loading