Say I want to add an argument to all functions in a module, and function calls to other functions in the module pass on the value of that additional argument. For example:
func.func public @f(%arg0: f32) -> f32 {
%0 = func.call @times_two(%arg0) : (f32) -> f32
func.return %0 : f32
}
func.func private @times_two(%arg0: f32) -> f32 {
%0 = arith.addf %arg0, %arg0 : f32
func.return %0 : f32
}
becomes:
func.func public @f(%arg0: i32, %arg1: f32) -> f32 {
%0 = func.call @times_two(%arg0, %arg1) : (i32, f32) -> f32
func.return %0 : f32
}
func.func private @times_two(%arg0: i32, %arg1: f32) -> f32 {
%0 = arith.addf %arg1, %arg1 : f32
func.return %0 : f32
}
What is the best way to implement this?
I have explored two options, neither of which really satisfy me. One is to walk the IR manually and transform the relevant ops. This would look like this:
TrivialPatternRewriter rewriter(context);
for (auto &op : module.getBodyRegion().front()) {
if (auto funcOp = llvm::dyn_cast<FunctionOpInterface>(&op)) {
if (!funcOp.isExternal())
addGridArguments(funcOp, rewriter);
}
}
TrivialPatternRewriter
is copied from here; addGridArguments
adds arguments to the funcOp
. (I don’t have any problems implementing the latter function.)
What is still missing here is the modification of the func.call
ops. I guess I can do this in a similar way but with a recursive walk.
What I don’t like is that (1) it uses TrivialPatternRewriter
, which it “discouraged” according to the place I copied it from and (2) it doesn’t express the transformation as patterns and therefore, seems to be non-standard.
The second possibility is using patterns; however, I don’t know how to express this logic as patterns that are applied exactly once. I can write a pattern that adds arguments to a function but that pattern will continue to match after it has been applied, so the two pattern drivers that I know, DialectConversion
and GreedyPatternRewriteDriver
, will apply the pattern infinitely often.
Maybe I can get the behavior I want with GreedyRewriteConfig::maxIterations = 1
?
If so, I am still not completely happy: ideally, I would like to only apply the patterns and not also possible folders. This (or something similar) has been discussed here and no consensus seems to have emerged at the time due to lack of motivation. My hesitation towards using something that also folds is mainly for testing: I would like to understand test the effect of the patterns and nothing else. Also, I don’t see the implications of applying arbitrary folders to IR that doesn’t verify: individual patterns will leave the IR in an invalid state since the transformation of the func.func
op is done by a different pattern that the transformation of the func.call
ops to that function, and the module will only verify after all patterns have been applied.
What’s the best decision here? Is manual IR walking adequate? Is my hesitation towards folding unjustified? Or would it make sense to have a “apply greedily but don’t fold” driver (option) upstream? (If so, I can propose an implementation.)