-
Notifications
You must be signed in to change notification settings - Fork 445
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
Make type checking true read-only visitor #4829
Conversation
Removing of overheads seem to yield 5-10% improvements in runtime (I have not measured memory pressure, but it is clear it would be less):
And similar times for downstream projects:
|
Certainly the better solution would be making |
I have not yet checked why, but it seems we used to emit errors twice. E.g.
now we produce:
|
Ok, looks like there was a subtle bug within error reporter that is now simply not being triggered. The example code in question is: if (getContext()->node->is<IR::ActionListElement>()) {
typeError("%1% is not invoking an action", expression);
return expression;
} Before template <class T, typename = std::enable_if_t<Util::has_SourceInfo_v<T>>, typename... Args>
void diagnose(DiagnosticAction action, const int errorCode, const char *format,
const char *suffix, const T *node, Args &&...args) {
...
}
template <class T, typename = std::enable_if_t<Util::has_SourceInfo_v<T>>, typename... Args>
void diagnose(DiagnosticAction action, const int errorCode, const char *format,
const char *suffix, const T &node, Args &&...args) {
...
}
template <typename... Args>
void diagnose(DiagnosticAction action, const char *diagnosticName, const char *format,
const char *suffix, Args &&...args) {
...
} When we pass non-const So, in this PR we started to pass Frankly speaking, I do not understand why we're having that accept-by-reference overload at all, as only nodes are expected to have source info and all nodes are heap-allocated objects. I think we'd just remove it. |
Why not duplicate A further complication is that, by design, many IR objects have const pointers which requires a copy. |
Sorry, I do not follow. what do you mean as "duplicate"? Are you suggesting making a copy of 180k SLOC of implementation? |
On the note of
Can you implement another version of Transform that does lazy cloning which is then used by TypeInference? Do you also need to change the IR implementation or many parts of TypeInference? |
|
The IR is the same. Few things of
Well, this would require some rethought on how IR is created / modified. Note that for lazy cloning we'd need to:
These changes drastically the overall visitor concept, currently lots of things expect that everything is "fresh clone" and "there is also original node somewhere". |
DEFINE_PREORDER(IR::EntriesList) | ||
DEFINE_PREORDER(IR::Type_SerEnum) | ||
|
||
#undef DEFINE_PREORDER |
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.
Duplicated on line 162
/* | ||
The type of the member is initially set in the Type_SerEnum preorder visitor. | ||
Here we check additional constraints and we may correct the member. | ||
if (done()) return member; | ||
*/ |
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 seems inconsistent with other comment styles in the file -- should it be double-slash?
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, it should be. However, this was the original comment. I just moved the function to a different file (see https://github.com/p4lang/p4c/pull/4829/files#diff-316382055471bab8faf77716e614a5508e70e44c56e52096f08ff5ebe6ee7e9dL355). Also note that the original code had if (done()) return member
shortcut commented out. I have not dig into the logic why it was so required.
Verifying that I correctly understand the reason for this:
To avoid the second copy, would either of these be a viable solution:
|
I should add that since you said that the double-clone seems to have negligible impact, it's probably not worth attempting either of the suggestions in my previous comment. But can extend the TypeInferenceBase comment in typeChecker.h to explain that a double-clone is occurring in case someone is looking at this later on please? |
The reason why I said the impact is negligible is the following:
And yes, the additional double-cloning only happens in very few Hope this makes the things a bit more clear :) |
74172b3
to
1e3daeb
Compare
I've been trying to think of a way of doing this. One possibility would be to change the preorder/postorder methods to take and return a "smart" pointer type that holds both a const pointer to the original node and non-cost pointer to the clone that would initially be nullptr. The first modification of the node would clone it. Template magic would be involved to make this all work cleanly. For incremental improvement, the way to do this sort of thing is to start by writing a new Visitor subclass replacement for Transform (maybe called LazyTransform?) and then experiment with rewriting existing Transform subclasses to use it. A big complication is how it interacts with Visitor::Context -- we need the smart pointers in there too, but perhaps that could be managed by careful updating. |
It's for dealing with |
Another tricky part is various side maps: they may capture original const-node (e.g. in |
1e3daeb
to
64b4b8b
Compare
f362104
to
c27ea8f
Compare
decl = cloned; | ||
} | ||
} | ||
if (decl->initializer != nullptr) visit(decl->initializer); |
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.
There's no need to check for nullptr here, as calling visit on a nullptr is a noop.
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.
Yeah... but this was the original code :)
That's already tricky -- any Modifier?Transform that runs currently will invalidate any data structure outside the IR DAG that refers to IR nodes. Perhaps we need a way of registering such a data structure with the visitor infrastructure so that it can be automatically updated to reflect any clones that occur, but I'm not sure how that would best work. Some sort of callback to be called whenever a node is cloned (or replaced? Transform may completely replace a node with a newly created node that might not even be the same class) |
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
…pechecker as a Visitor with reduced overhead Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
Signed-off-by: Anton Korobeynikov <[email protected]>
e983e97
to
ea6a303
Compare
TypeChecking / TypeInference is the pass that is executed all the time during the compilation:
I counted on some downstream program that top-level (on P4Program node) TypeInference is called > 70 times. The nested invocations are much more numerous. Only in few cases (~2 out of these 70) is does some transformations, e.g. insert casts.
The real issue is that TypeInference is a Transform: so it does cloning of the IR even in so-called "read-only mode" and nothing really changes.
This PR restructures the code in such a way, that base functionality could be called from both
Inspector
andTransform
. The only downside is that forTransform
case we might have double-cloning, but this seems to cause negligible impact.As a result, the functionality is split into:
ReadOnlyTypeInference
which is anInspector
. It is used forTypeCheck
, insideMethodInstance::resolve
and read-write TypeInference learnerTypeInference
that is aTransform
. For debugging purposes I left thereadOnly
interface here as the cost to support it is essentially free.Overall we are saving:
clone
for each node during type checking and use ofTypeInference
in read-only matterTransform
structures and much less complicated logicFixes #4815