[RFC] Codegen new pass manager pipeline construction design

Noticed PassManager::addPass has an overload for pass manager to merge two same type pass managers, I’m wondering if we could do something like:

class TargetPassBuilder {
protected:
  // Or shadow `PassManager`s and related `createXXXPassAdaptor`s
  class ModulePassManagerWrapper {
  public:
    template<typename PassT>
    std::enable_if_t<isNotModulePassManagerWrapper>
    addPass(PassT &&P) {
      static_assert(/* not pass manager or adaptor */);
      PassNames.push_back(PassT::name());
      PassManagers.push_back(ModulePassManager()
        .addPass(std::forward<PassT>(P)));
    }
    template<typename PassT>
    std::enable_if_t<isModulePassManagerWrapper>
    addPass(PassT &&P) { /* merge two wrapper */}
    void addFunctionPassManagerWrapper(FunctionPassManagerWrapper &&FPMW) {...}
  private:
    std::vector<StringRef> PassNames;
    std::vector<std::variant<ModulePassManager, FunctionPassManager,
        LoopPassManager, MachineFunctionPassManager>> PassManagers;
  };
  ModulePassManagerWrapper MainWrapper;
public:
  ModulePassManager buildPipeline() {
    ModulePassManagerWrapper MPMW;
    // Do pipeline construction.
    ...
    // Filter passes with MPMW because we have all pass names in order.
    // Construct PassManager from MPMW manually,
    // pass manager will merge pass manager automatically in `addPass`.
  }
};

Users must do:

void TargetPassBuilder::buildSomePart() {
  ModulePassManagerWrapper MPMW;
  MPMW.addPass(Pass1);
  MPMW.addPass(Pass2);
  FunctionPassManagerWrapper FPMW;
  FPMW.addPass(Pass1);
  FPMW.addPass(Pass1);
  MPWM.addFunctionPassWrapper(std::move(FPMW));
  // Same for LoopPassManagerWrapper and MachineFunctionPassManagerWrapper.
  MainWrapper.addPass(std::move(MPMW));
}

Then we can both filter the pipeline and force user concerning pass nesting explicitly.


We need a very flexible way to extend pass pipeline, we may introduce many extension points to emulate virtual functions in TargetPassConfig. I considered methods like inject{Before,After}<SomePass>(...) but it seems a bit too flexible.


Miscellaneous:
Currently codegen pipeline use its own optimization level implementation and it is overlapped with "llvm/Passes/OptimizationLevel.h", should we standardize on using OptimizationLevel?

Ping @aeubanks