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
Fix def_use/SimplifyDefUse to deal with extern methods properly #1284
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is making some assumptions about when abstract methods may be called. An abstract method can be called even asynchronously, i.e., not by the P4 dataplane program, so it should not be able to reach into its environment at all.
Maybe we need to formalize the semantics of abstract methods. One possibility would be to consider adding a special annotation @synchronous(caller)
to indicate that a specific abstract method can only be called by a specific extern method and never at some other time. For these methods we could allow perhaps capturing the environment.
|
||
// we control the traversal order manually, so we always 'prune()' | ||
// (return false from preorder) | ||
|
||
bool preorder(const IR::ParserState* state) override { | ||
LOG3("FU Visiting " << dbp(state)); | ||
LOG3("FU Visiting state " << state->name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing that dbp does which I found handy is to print the ID of the node besides the name. That makes it easy to cross-reference with a dump of a program generated using --top4 -v.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find the id rarely useful, and dbp also just prints the id and type and none of the contents of the node (like the state name), making it mostly useless in my experience. But of course, debugging methods differ, so YMMV.
frontends/p4/simplifyDefUse.cpp
Outdated
} else if (mcd.instance->isApply()) { | ||
auto am = mcd.instance->to<ApplyMethod>(); | ||
if (am->isTableApply()) { | ||
auto table = am->object->to<IR::P4Table>(); | ||
callee = table; | ||
} | ||
} | ||
} else if (auto em = mcd.instance->to<ExternMethod>()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code seems to be the same as in def_use.cpp. Maybe it can be refactored to be written only once?
midend/local_copyprop.cpp
Outdated
// method in the object, so act as if all of them are applied | ||
LOG3("extern method call " << name); | ||
name = obj + '.'; | ||
for (auto it = methods.upper_bound(name); it != methods.end(); ++it) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is conservative enough. The virtual methods could also be invoked asynchronously at any time, e.g., by a request from the control-plane. We have not agreed that abstract methods are only invoked when some other extern method is invoked.
Also, consider action profiles: the action selector will be invoked when a table is invoked, without any other method being called explicitly.
So I think you should always consider the abstract methods as "live" and scan them. You know nothing about the context they execute in, though; they could all execute, or one of them could execute multiple times. This code should not be necessary, you should always visit them.
|
||
Virtual() cntr = { // implementation | ||
bit<16> f(in bit<16> ix) { // abstract method implementation | ||
return (ix + local); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChrisDodd : I have an issue filed to forbid access to the context in an abstract method. We cannot give a semantics to this code. I don't think we should allow this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately we can't do that -- the whole point of abstract methods in per-control instances is to get access to the context -- if we disallow that, we might as well require all externs instances be global.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need an annotation here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @synchronous annotation was added in to the abstract method declaration in the extern above.
I think what we really need is a hook for backends to provide per-extern (and per method) information about when the method(s) might be invoked, so as to allow proper flow analysis. So this is incomplete, but an improvement over the existing code which assumed that abstract methods could never be called in the middle of the control apply block. |
What is wrong with my proposal for a |
I don't think @synchronous is enough. We could create a little language in annotations -- annotate each extern method with the set of other methods it could invoke. If an asynchronous method could be invoked anywhere, I'm not sure how to deal with it in these passes, however. |
9a4ee25
to
b68210d
Compare
878020d
to
e4b7a08
Compare
0d8cdc3
to
b491781
Compare
So I've updated the code to use @synchronous annotations to indicate which methods might call abstract methods, so accesses to in-scope vars can be anayzed a bit better. I would like to check this in as it fixes some problems we see with dead-code eliminating metadata fields that are only used by abstract method implementations. @mbudiu-vmw any other comments? |
On vacation this week with no laptop |
2417681
to
15d9933
Compare
@@ -404,10 +404,15 @@ void ResolveReferences::postorder(const IR::Type_Method *t) { | |||
|
|||
bool ResolveReferences::preorder(const IR::Type_Extern *t) { | |||
refMap->usedName(t->name.name); | |||
addToContext(t->typeParameters); return true; } | |||
// FIXME -- should the typeParamters be part of the extern's scope? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don’t think it will work without them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently they're not part of the Type_Extern's scope, so need to be added as a separate scope (which is what this part of the change does)
@@ -932,3 +934,13 @@ bool ComputeWriteSet::preorder(const IR::MethodCallStatement* statement) { | |||
} | |||
|
|||
} // namespace P4 | |||
|
|||
// functions for calling from gdb | |||
void dump(const P4::StorageLocation *s) { std::cout << *s << std::endl; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If these implement IHasDbPrint there should already exist a dump, but indeed gdb sometimes has trouble finding the dump.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, gdb seems to be unable to deal with virtual base classes properly (or at least to be able to call function overloads based on them), so I ended up adding all these overloads to make them callable.
frontends/p4/methodInstance.h
Outdated
@@ -129,6 +129,11 @@ class ExternMethod final : public MethodInstance { | |||
const IR::Method* method; | |||
const IR::Type_Extern* originalExternType; // type of object method is applied to | |||
const IR::Type_Extern* actualExternType; // with type variables substituted | |||
|
|||
/// Set of IR::Method and IR::Function objects that may be called by this method. | |||
// If this method is abstract, will consist of (just) the concrete implemenation, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo
|
||
Virtual() cntr = { // implementation | ||
bit<16> f(in bit<16> ix) { // abstract method implementation | ||
return (ix + local); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you need an annotation here?
@@ -6,18 +6,29 @@ extern bit<1> f(inout bit<1> x, in bit<1> b); | |||
control c(out H[2] h); | |||
package top(c _c); | |||
control my(out H[2] s) { | |||
bit<32> a_0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes suggest that some analysis has become less precise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code here changed because we're now assuming that the unknown method call f
might depend on the locals. However, f is a global extern function, so we should probably assume it only depends on/modifies things passed to it explicitly.
93f070b
to
0c7cf9c
Compare
- Track extern method calls, assuming that any call to any non-abstract method might trigger any abstract method implementation - local_copyprop fixed for the same assumption. - propagate reads/writes from callee to caller properly. - @synchronized annotation for abstract methods
0c7cf9c
to
fcf1062
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought a bit about it, and i think the way we should do some of this is to have an abstract base class for the current architecture which can, for example, tell you which functions are pure (e.g. Hash). The architecture should be initialized very early in the compilation.
IMO we should be doing more in the architecture P4 file to describe how the architecture works -- perrhaps (simplified) bodies for extern methods and functions that summarize what they do and can be analyzed by common frontend/midend passes to drive their transformations. I'm not a big fan of duplicating stuff between C++ classes and architecture files in a way that needs to be carefully managed but is subject to breakage when someone makes a change -- all dependencies of this nature should be strongly delineated by automatic tests that will diagnose the problem automatically when someone breaks something. |
- Track extern method calls, assuming that any call to any non-abstract method might trigger any abstract method implementation - local_copyprop fixed for the same assumption. - propagate reads/writes from callee to caller properly. - @synchronized annotation for abstract methods
- Track extern method calls, assuming that any call to any non-abstract method might trigger any abstract method implementation - local_copyprop fixed for the same assumption. - propagate reads/writes from callee to caller properly. - @synchronized annotation for abstract methods - adjust virtual.p4 test to reflect problematic case of non-synchronous function
- Track extern method calls, assuming that any call to any non-abstract method might trigger any abstract method implementation - local_copyprop fixed for the same assumption. - propagate reads/writes from callee to caller properly. - @synchronized annotation for abstract methods - adjust virtual.p4 test to reflect problematic case of non-synchronous function
- Track extern method calls, assuming that any call to any non-abstract method might trigger any abstract method implementation - local_copyprop fixed for the same assumption. - propagate reads/writes from callee to caller properly. - @synchronized annotation for abstract methods - adjust virtual.p4 test to reflect problematic case of non-synchronous function
… (#1652) * Fix def_use/SimplifyDefUse to deal with extern methods properly (#1284) - Track extern method calls, assuming that any call to any non-abstract method might trigger any abstract method implementation - local_copyprop fixed for the same assumption. - propagate reads/writes from callee to caller properly. - @synchronized annotation for abstract methods - adjust virtual.p4 test to reflect problematic case of non-synchronous function
I just noticed |
@apinski-cavium we have long wanted to do that in the spec, but never got around to it: p4lang/p4-spec#943 |
non-abstract method might trigger any abstract method implementation