How to deal with parallel FuncOp insertion in the ModuleOp

We have a some use cases in conversion in Flang where we transform operation into runtime calls. At this time, we need to introduce the corresponding FuncOp that represent the runtime function.

So basically what we do is smth like:

if (auto funcOp = mod.lookupSymbol<mlir::func::FuncOp>(name))
  return funcOp;
mlir::OpBuilder modBuilder(module.getBodyRegion());  modBuilder.setInsertionPointToEnd(module.getBody());
auto result = modBuilder.create<mlir::func::FuncOp>(loc, name, type, attrs);

When we have conversion that run on operation we can end up with race condition and multiple FuncOp being created for the same runtime function.

We have couple of solutions to this problem but we wanted to double check if someone has anything better.

  1. Have a Module pass before that insert the needed FuncOp for runtime function.
  2. Add a lock in the conversion and acquire the lock around FuncOp lookup/creation.
  3. Switch the conversion to be runOnModule instead of runOnOperation but this seems a bit overkill.

We are currently experiencing we solution number 2 and it works fine but we are wondering if it will not have more serious implication.

My understanding is that (3) is the recommended solution. (1) can also work, so you can add all the possible functions in (1) and then delegate the replacement to another pass that just uses existing functions.

I think option 2 is probably safe, other than the fact that it’s violating the expectations of a pass Pass Infrastructure - MLIR, but I suspect that it is not a valuable optimization while being significantly more complicated/dangerous.

Running with runOnModule clearly indicates that the pass is relying on context at the module level and prevents situations where parallel work would result in mistakes. Option 2 is taking that responsibility onto yourself which solves the race condition concern but does not handle any other implementation details that might come out of the documented expectations of a pass, such as the (imaginary) infrastructure only storing a log of transformations, only committing them later, and aborting when it sees conflicting transformations because the lookup did not see transformations currently only in the log.