Skip to content

Commit 6b8e658

Browse files
committed
Check for out/inout bindings aliased with uses
- pass that finds captured expressions in callable (symbols defined outside the callable) - modify some failing tests to not have the error - add to p4smith's 'KNOWN_BUGS' for tests, as it generates code that fails this check Signed-off-by: Chris Dodd <[email protected]>
1 parent a972904 commit 6b8e658

File tree

70 files changed

+334
-479
lines changed

Some content is hidden

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

70 files changed

+334
-479
lines changed

backends/p4tools/modules/smith/scripts/compilation-test.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ KNOWN_BUGS=(
1212
"Cannot evaluate initializer for constant"
1313
"Null expr"
1414
"error: retval_1: declaration not found" # V1model failure.
15+
"error: \(in\)\?out argument binds value accessed by callee"
1516
)
1617

1718
# Function to check if an error is triggered by a known bug.

frontends/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
set (P4_FRONTEND_SRCS
1616
p4/actionsInlining.cpp
1717
p4/callGraph.cpp
18+
p4/checkArgAlias.cpp
1819
p4/checkConstants.cpp
1920
p4/checkCoreMethods.cpp
2021
p4/checkNamedArgs.cpp
@@ -103,6 +104,7 @@ set (P4_FRONTEND_HDRS
103104
p4/actionsInlining.h
104105
p4/alias.h
105106
p4/callGraph.h
107+
p4/checkArgAlias.h
106108
p4/checkConstants.h
107109
p4/checkCoreMethods.h
108110
p4/checkNamedArgs.h

frontends/p4/checkArgAlias.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
Copyright 2025 Nvidia, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#include "checkArgAlias.h"
18+
19+
#include "methodInstance.h"
20+
21+
namespace P4 {
22+
23+
// find the enclosing 'location' expression from the current context. That
24+
// is, any Member(s) or ArrayIndex(s) that are a parents of the current node.
25+
const IR::Expression *CheckArgAlias::FindCaptures::getRefCtxt() {
26+
auto *ctxt = getChildContext();
27+
for (; auto *p = ctxt->parent; ctxt = p) {
28+
if (p->node->is<IR::Member>()) continue;
29+
if (p->node->is<IR::ArrayIndex>() && p->child_index == 0) continue;
30+
break;
31+
}
32+
return ctxt->node->to<IR::Expression>();
33+
}
34+
35+
bool CheckArgAlias::FindCaptures::preorder(const IR::PathExpression *pe) {
36+
const Context *ctxt = nullptr;
37+
auto last = visiting.end();
38+
--last;
39+
while (auto scope = findOrigCtxt<IR::INamespace>(ctxt)) {
40+
auto rv = lookup(scope, pe->path->name, ResolutionType::Any);
41+
if (!rv.empty()) break;
42+
if (last->second == scope->getNode()) --last;
43+
}
44+
auto expr = getRefCtxt();
45+
for (++last; last < visiting.end(); ++last) {
46+
LOG4(expr << " escapes from " << DBPrint::Brief << last->first);
47+
result[last->first][pe->path->name].push_back(expr);
48+
}
49+
return true;
50+
}
51+
52+
bool CheckArgAlias::FindCaptures::preorder(const IR::IApply *ia) {
53+
LOG3("CheckArgAlias::preorder(IA): " << DBPrint::Brief << ia);
54+
auto *d = ia->to<IR::IDeclaration>();
55+
visiting.push_back(std::make_pair(d, d->getNode()));
56+
return true;
57+
}
58+
59+
bool CheckArgAlias::FindCaptures::preorder(const IR::IFunctional *fn) {
60+
LOG3("CheckArgAlias::preorder(IF): " << DBPrint::Brief << fn);
61+
auto *d = fn->to<IR::IDeclaration>();
62+
visiting.push_back(std::make_pair(d, d->getNode()));
63+
return true;
64+
}
65+
66+
const IR::Expression *CheckArgAlias::Check::baseExpr(const IR::Expression *e, int *depth) {
67+
if (depth) ++*depth;
68+
if (auto *mem = e->to<IR::Member>()) return baseExpr(mem->expr, depth);
69+
if (auto *ai = e->to<IR::ArrayIndex>()) return baseExpr(ai->left, depth);
70+
return e;
71+
}
72+
73+
int CheckArgAlias::Check::baseExprDepth(const IR::Expression *e) {
74+
int depth = 0;
75+
baseExpr(e, &depth);
76+
return depth;
77+
}
78+
79+
bool CheckArgAlias::Check::overlaps(const IR::Expression *e1, const IR::Expression *e2) {
80+
while (auto *ai = e1->to<IR::ArrayIndex>()) e1 = ai->left;
81+
while (auto *ai = e2->to<IR::ArrayIndex>()) e2 = ai->left;
82+
auto *m1 = e1->to<IR::Member>();
83+
auto *m2 = e2->to<IR::Member>();
84+
if (m1 && m2) {
85+
if (m1->expr->type == m2->expr->type) return m1->member == m2->member;
86+
if (baseExprDepth(m1->expr) > baseExprDepth(m2->expr))
87+
return overlaps(m1->expr, e2);
88+
else
89+
return overlaps(e1, m2->expr);
90+
} else if (m1) {
91+
return overlaps(m1->expr, e2);
92+
} else if (m2) {
93+
return overlaps(e1, m2->expr);
94+
}
95+
BUG_CHECK(e1->is<IR::PathExpression>(), "'%1%' not a PathExpression", e1);
96+
BUG_CHECK(e2->is<IR::PathExpression>(), "'%1%' not a PathExpression", e2);
97+
BUG_CHECK(e1->equiv(*e2), "'%1%' and '%2%' are different", e1, e2);
98+
return e1->equiv(*e2);
99+
}
100+
101+
bool CheckArgAlias::Check::preorder(const IR::MethodCallExpression *mce) {
102+
LOG3("CheckArgAlias::Check::preorder(MCE): " << mce);
103+
auto *mi = MethodInstance::resolve(mce, this, self.typeMap);
104+
BUG_CHECK(mi, "MethodInstance::resolve failed");
105+
if (!self.captures.any(mi->callee())) return true;
106+
auto arg = mi->expr->arguments->begin();
107+
auto *params = mi->actualMethodType->parameters;
108+
for (auto it = params->begin(); it < params->end(); ++it, ++arg) {
109+
if (arg == mi->expr->arguments->end()) {
110+
// at the end of the argument list -- rest of params must be directionless
111+
// and this must be an action call
112+
BUG_CHECK(mi->is<P4::ActionCall>(), "not an action call?");
113+
break;
114+
}
115+
auto *param = *it;
116+
if (param->direction == IR::Direction::None) continue;
117+
if (param->direction == IR::Direction::In) continue;
118+
auto *exp = (*arg)->expression;
119+
while (auto *sl = exp->to<IR::AbstractSlice>()) exp = sl->e0;
120+
auto *pe = baseExpr(exp)->to<IR::PathExpression>();
121+
BUG_CHECK(pe, "not a PathExpression");
122+
for (auto *cap : self.captures(mi->callee(), pe->path->name)) {
123+
if (overlaps(exp, cap)) {
124+
error(ErrorType::ERR_INVALID, "%1%%2% binds value accessed by callee %3%%4%",
125+
param->direction == IR::Direction::InOut ? "inout argument" : "out argument",
126+
(*arg)->srcInfo, mi->callee(), cap->srcInfo);
127+
// only trigger once per call
128+
return true;
129+
}
130+
}
131+
}
132+
return true;
133+
}
134+
135+
} // namespace P4

frontends/p4/checkArgAlias.h

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
Copyright 2025 Nvidia, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#ifndef FRONTENDS_P4_CHECKARGALIAS_H_
18+
#define FRONTENDS_P4_CHECKARGALIAS_H_
19+
20+
#include "frontends/common/resolveReferences/resolveReferences.h"
21+
#include "ir/ir.h"
22+
#include "ir/pass_manager.h"
23+
#include "typeChecking/typeChecker.h"
24+
#include "typeMap.h"
25+
26+
namespace P4 {
27+
28+
class CheckArgAlias : public PassManager {
29+
TypeMap *typeMap;
30+
class FindCaptures : public Inspector, public ResolutionContext {
31+
std::vector<std::pair<const IR::IDeclaration *, const IR::Node *>> visiting;
32+
profile_t init_apply(const IR::Node *root) {
33+
auto rv = Inspector::init_apply(root);
34+
// initialize visiting stack with (just) sentinel value;
35+
visiting.resize(1);
36+
visiting.back().first = nullptr;
37+
visiting.back().second = nullptr;
38+
return rv;
39+
}
40+
41+
const IR::Expression *getRefCtxt();
42+
43+
bool preorder(const IR::PathExpression *);
44+
bool preorder(const IR::Type_Name *) { return false; }
45+
46+
bool preorder(const IR::IApply *);
47+
bool preorder(const IR::IFunctional *);
48+
49+
// FIXME -- would be nice if dynamic dispatch could handle this automatically
50+
bool preorder(const IR::Node *n) {
51+
if (auto *ia = n->to<IR::IApply>()) return preorder(ia);
52+
if (auto *fn = n->to<IR::IFunctional>()) return preorder(fn);
53+
return true;
54+
}
55+
void postorder(const IR::Node *n) {
56+
if (!visiting.empty() && visiting.back().second == n) visiting.pop_back();
57+
}
58+
59+
void end_apply(const IR::Node *) { BUG_CHECK(visiting.size() == 1, "failed to finish?"); }
60+
void end_apply() { visiting.clear(); }
61+
62+
using result_t = std::vector<const IR::Expression *>;
63+
std::map<const IR::IDeclaration *, std::map<cstring, result_t>> result;
64+
65+
public:
66+
const result_t &operator()(const IR::IDeclaration *d, cstring name) const {
67+
static result_t empty;
68+
auto i1 = result.find(d);
69+
if (i1 == result.end()) return empty;
70+
auto i2 = i1->second.find(name);
71+
if (i2 == i1->second.end()) return empty;
72+
return i2->second;
73+
}
74+
bool any(const IR::IDeclaration *d) const { return result.find(d) != result.end(); }
75+
} captures;
76+
77+
class Check : public Inspector, public ResolutionContext {
78+
const CheckArgAlias &self;
79+
const IR::Expression *baseExpr(const IR::Expression *, int * = nullptr);
80+
int baseExprDepth(const IR::Expression *e);
81+
bool overlaps(const IR::Expression *e1, const IR::Expression *e2);
82+
bool preorder(const IR::MethodCallExpression *);
83+
84+
public:
85+
explicit Check(const CheckArgAlias &s) : self(s) {}
86+
};
87+
88+
public:
89+
explicit CheckArgAlias(TypeMap *tm) : typeMap(tm) {
90+
addPasses({&captures, new ReadOnlyTypeInference(typeMap), new Check(*this)});
91+
}
92+
};
93+
94+
} // namespace P4
95+
96+
#endif /* FRONTENDS_P4_CHECKARGALIAS_H_ */

frontends/p4/frontend.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ limitations under the License.
2626
#include "lib/nullstream.h"
2727
// Passes
2828
#include "actionsInlining.h"
29+
#include "checkArgAlias.h"
2930
#include "checkConstants.h"
3031
#include "checkCoreMethods.h"
3132
#include "checkNamedArgs.h"
@@ -231,6 +232,7 @@ const IR::P4Program *FrontEnd::run(const CompilerOptions &options, const IR::P4P
231232
}),
232233
new CheckCoreMethods(&typeMap),
233234
new StaticAssert(&typeMap),
235+
new CheckArgAlias(&typeMap),
234236
});
235237
metricsPassManager.addUnusedCode(passes, true);
236238
passes.addPasses({

frontends/p4/methodInstance.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class MethodInstance : public InstanceBase {
8484
with instantiated type parameters. */
8585
const IR::Type_MethodBase *actualMethodType;
8686
virtual bool isApply() const { return false; }
87+
virtual const IR::IDeclaration *callee() const { return object; }
8788

8889
/** @param useExpressionType If true, the typeMap can be nullptr,
8990
* and then mce->type is used. For some technical reasons
@@ -141,6 +142,7 @@ class ApplyMethod final : public MethodInstance {
141142
const IR::IApply *applyObject;
142143
bool isApply() const override { return true; }
143144
bool isTableApply() const { return object->is<IR::P4Table>(); }
145+
const IR::IDeclaration *callee() const override { return applyObject->to<IR::IDeclaration>(); }
144146

145147
DECLARE_TYPEINFO(ApplyMethod, MethodInstance);
146148
};
@@ -222,6 +224,7 @@ class ActionCall final : public MethodInstance {
222224
/// Generate a version of the action where the parameters in the
223225
/// substitution have been replaced with the arguments.
224226
const IR::P4Action *specialize(const DeclarationLookup *refMap) const;
227+
const IR::IDeclaration *callee() const override { return action; }
225228

226229
DECLARE_TYPEINFO(ActionCall, MethodInstance);
227230
};
@@ -244,6 +247,7 @@ class FunctionCall final : public MethodInstance {
244247

245248
public:
246249
const IR::Function *function;
250+
const IR::IDeclaration *callee() const override { return function; }
247251

248252
DECLARE_TYPEINFO(FunctionCall, MethodInstance);
249253
};

ir/dbprint-p4.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,9 @@ void IR::ActionFunction::dbprint(std::ostream &out) const {
138138
}
139139

140140
void IR::P4Action::dbprint(std::ostream &out) const {
141-
out << annotations << "action " << name << "(";
141+
out << annotations << "action " << name;
142+
if (dbgetflags(out) & Brief) return;
143+
out << "(";
142144
const char *sep = "";
143145
for (auto arg : parameters->parameters) {
144146
out << sep << arg->direction << ' ' << arg->type << ' ' << arg->name;
@@ -214,6 +216,7 @@ void IR::V1Control::dbprint(std::ostream &out) const {
214216
}
215217
void IR::P4Control::dbprint(std::ostream &out) const {
216218
out << "control " << name;
219+
if (dbgetflags(out) & Brief) return;
217220
if (type->typeParameters && !type->typeParameters->empty()) out << type->typeParameters;
218221
if (type->applyParams) out << '(' << type->applyParams << ')';
219222
if (constructorParams) out << '(' << constructorParams << ')';

0 commit comments

Comments
 (0)