-
Notifications
You must be signed in to change notification settings - Fork 441
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
Implemented DeclarationCopyPropagation pass #3082
base: main
Are you sure you want to change the base?
Conversation
51f0175
to
72065b4
Compare
/// It checks both cases because it needs to retain the casts. | ||
/// @return true if a constant is found. | ||
bool DoDeclarationCopyPropagation::checkConst(const IR::Expression* expr) { | ||
if (expr->is<IR::Constant>()) { |
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 will only discover integer constants, but there are many other types of constants.
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 typechecker has a very compreshensive constant detection algorithm.
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, sorry. I have now added support for other types of constants.
bool working = false; | ||
|
||
/// If set to true limits the pass to only check if the used | ||
/// variables are initialized. It assumes that all of them are |
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 understand the comment. Please clarify.
Variables can be initialized also from expressions depending on parameters and by calling functions.
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.
When checkOnly is true the pass checks if used variables have declarations, and if they have an initializer sets them as compile time constant, even if they aren't. This is done to postpone compile time constant check and wait for TypeInference to insert casts and convert structures from ListExpression to StructExpression. When this flag is true no propagation is done.
Propagating variables initialized by calling functions is currently disabled because I think that it is too early in the frontend to find the return values and propagate them without complicating things further.
@@ -172,11 173,17 @@ const IR::P4Program *FrontEnd::run(const CompilerOptions &options, const IR::P4P | |||
new ResolveReferences(&refMap), // check shadowing | |||
new Deprecated(&refMap), | |||
new CheckNamedArgs(), | |||
new DeclarationCopyPropagation(&refMap, &typeMap, true), |
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 makes me think that we have to separate the typeMap into two pieces - one for constants and one for types.
The one for types can just subclass the one for constants. Also, you are not passing information through the typeMap between passes, so it probably can be allocated locally in the DeclarationCopyPropagation and not passed as an argument.
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, I think that separating the maps will be useful too, but it is not crucial for my solution.
TypeMap is needed to postpone the compile time constant check in TypeInference, by adding declarations with initializers in the constants set. After that all variables that are initialized with constants will be propagated, and TypeInference will do it's work, so passing the information further is not needed.
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.
Indeed, it is not, but since you are the first user of the reduced functionality it is a logical place to do it here.
It won't be too much work: just create a class that contains the constants HashMap and the APIs to insert and lookup. Then make typeMap inherit from that class.
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.
Can you please explain it a bit further?
I can create a class that contains the constants and will be the parent of the TypeMap class, but I would still need to use the typeMap in DeclarationCopyPropagation so that TypeInference could see 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.
Are you assuming that the TypeInference pass from line 180 will reuse that information?
My guess is that it won't - it will recompute it from scratch using its own rules.
If you have propagated the constants it should not need the information anyway.
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 information will be used in that TypeInference when checking if variables are compile time constant. This is needed only here because the DeclarationCopyPropagation on line 176 won't propagate any constants. The propagation is done in the PassRepeated DeclarationCopyPropagation, after the first TypeInference has passed.
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 see. That could be a problem is additional passes are inserted in between that information could be lost.
I wonder whether a separate data structure should be used for that purpose (but that's not easy).
A second problem is that any changes in the IR between these two passes may invalidate this information: if a child of any of the nodes you analyze will change, the nodes will be replaced by clones, so they won't be found in the TypeMap.
Ideally you should propagate the constants right away, immediately after you detect them. But I assume you need type information. Can't you put the analysis and propagation next to each other?
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 intended that the passes in between, including DeclarationCopyPropagation, would be viewed as a group and not changed, but you are right, additional passes could be inserted.
Yes, first it was the plan to do everything right away, but I needed TypeInference to check types and do other work, and only then propagate. The problem was with compile time constant errors, so I needed to split the pass calls to solve this.
Now that I think of it, maybe a good solution would be to add a skipCompileTimeConstantsCheck flag in TypeInference. That way I could postpone the compile time constant check, and DeclarationCopyPropagation on line 176 wouldn't be needed anymore, everything could be done in PassRepeated afterwards. Also, in that case, the use of TypeMap could be completely avoided.
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.
Type checking uses the constant property to check that some expressions are compile-time constants, e.g., constructor parameters. It may be possible to split this functionality in a separate pass, but it may not be easy.
Your proposal to avoid giving errors in the first type-checking pass is a possibility. The type-checking pass is accumulating a few such boolean flags, which will make the code harder to understand, but they are relatively local.
If you make this change we should move from having boolean arguments in the constructor, that is completely unreadable, please define some enums with the options for typechecking.
enum class TypeCheckingOptions {
SkipConstantCheck,
ReadOnly,
}
We could put these in the typeMap - in fact, this is what setStrictStruct does.
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.
Thank you for your advice! I will try to do that and reply when I'm done.
@@ -0,0 1,120 @@ | |||
|
|||
/* |
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 to modify this copyright header?
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, sorry about that.
|
||
control C1(inout bit<16> x) | ||
{ | ||
test2 tst2 = {1, {2, {3, 4}}, {5, {6, {7, 8}}, {{9, {10, 11}}, true}}}; |
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 is not even a structexpression, it is a listexpression, but it will be turned into a structexpression after type checking.
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, I know. It was easier for me to initialize the struct in this form. I have now added a variable initialized with a StructExpression in the example.
It propagates declared variables in the locals part of controls. Implemented as a fix for p4lang#2844.
72065b4
to
2be7f1e
Compare
bit<32> h = 32w2; | ||
bit<32> z = 32w12; | ||
bit<32> all = 32w20; | ||
bit<32> fun = f(h, 32w12); |
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.
in this test please add one more initializer after fun. I expect that one will not be optimized.
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've added it. You are right, it won't be optimized.
@@ -172,11 173,17 @@ const IR::P4Program *FrontEnd::run(const CompilerOptions &options, const IR::P4P | |||
new ResolveReferences(&refMap), // check shadowing | |||
new Deprecated(&refMap), | |||
new CheckNamedArgs(), | |||
new DeclarationCopyPropagation(&refMap, &typeMap, true), |
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.
Indeed, it is not, but since you are the first user of the reduced functionality it is a logical place to do it here.
It won't be too much work: just create a class that contains the constants HashMap and the APIs to insert and lookup. Then make typeMap inherit from that class.
Implemented DeclarationCopyPropagation pass as a fix for case 2 of issue #2844.
It propagates variables declared with a constant in the locals part of controls.
The goal is to propagate variables that are declared with a constant value so that they can be used
in places where compile time values are necessary. It also optimizes the code by making the variables
unneeded if they are completely propagated.
For example:
before propagation
after propagation:
Before propagation this example wouldn't work because
counter_size
cannot evaluate to a compile time constant.After propagation the value of
counter_size
will be placed directly where it is needed, and it will compile without an error.It works only in control locals because the declared values cannot be changed until the apply block,
and minimal copy propagation that is needed to fix the issue is done, so we are sure that it doesn't make things
worse and more complicated.
Also, because of the same reason it is currently disabled in actions and tables.
The pass is called two times in the frontend, first before the first TypeInference, with checkOnly flag set to true.
When this flag is true the pass checks declarations for used variables and if they have an initializer, sets them as compile time constant, even if they aren't. No propagation is done during this run. It is implemented like this to postpone the first compile time check, and wait for TypeInference to insert casts and convert structures from ListExpression to StructExpression, to make it possible to properly find values of struct members.
Second call of the pass is in the form of PassRepeated, in combination with ConstantFolding. In this case checkOnly flag is set to false, and propagation is done until convergence.
Edit:
There was a problem with infinite iterations of PassRepeated with ConstantFolding when there is a Declaration_Constant with a cast. Because the cast is detected, the initializer is cloned, and in every iteration has a different pointer, which resulted in an infinite loop. The initializers are still exactly the same, but with different pointers, so changing the check from shallow to deep equivalence fixed the issue.