Skip to content

Conversation

@paperchalice
Copy link
Contributor

This implementation supports --start/stop-before/after options by hijacking original pass managers. With injected-class-name, user should not feel any difference from original pass manager in most cases.

Except virtual functions to add necessary passes (e.g. instruction select pass), the only method to extend the pipeline is injectBefore, which accept a callback to extend pipeline at the specified point. The return type of the callback is depend on the selected pass

injectBefore<SomeFunctionPass>([](){
  FunctionPassManager FPM;
  // Add passes you want.
  return FPM;
});

// If you really want to add module pass between machine function passes...
injectBefore<SomeMachineFunctionPass, ModulePassManager>([](){
  ModulePassManager MPM;
  MachineFunctionPass MFPM;
  // Add passes you want.
  MPM.createModuleToFunctionPassAdaptor(createFunctionToMachineFunctionPassAdaptor(std::move(MFPM)));
  return MPM;
});

@paperchalice paperchalice marked this pull request as ready for review April 25, 2025 08:22
@optimisan
Copy link
Contributor

injectBefore and disablePass methods are independent of addPass calls which is nice.

But there are no virtual methods, so are we removing distinct phases (like addPreEmitPasses()) completely and only using injectBefore() now?
If we don't want overrides (virtual/CRTP) we could add "phase marker" passes (that are dummy passes like ISelPrepareBegin()) to be used with injectBefore.

@paperchalice
Copy link
Contributor Author

paperchalice commented Apr 29, 2025

But there are no virtual methods, so are we removing distinct phases (like addPreEmitPasses()) completely and only using injectBefore() now?

IMHO some phases here is not clear, the example is pre emit, we have addPreEmitPasses and addPreEmitPasses2() thus here only provides injectBefore.

If we don't want overrides (virtual/CRTP) we could add "phase marker" passes (that are dummy passes like ISelPrepareBegin()) to be used with injectBefore.

Yeah pre isel is one of phases injectBefore() can't handle easily, may add them in future.


To be more radical, I even want to use type erasure to ensure backends only add dag isel and asm printer to replace the getSelectionDAGISelPass in current code.

@paperchalice
Copy link
Contributor Author

paperchalice commented May 5, 2025

Ping @aeubanks @arsenm

@paperchalice paperchalice force-pushed the target-pass-builder branch from 5e3864a to b4867b3 Compare May 8, 2025 07:16
@paperchalice
Copy link
Contributor Author

Support dummy injection points now, they are PreISel, PostBBSections and PreEmit.

@cdevadas
Copy link
Collaborator

cdevadas commented May 8, 2025

Instead of force pushing, can you keep separate commits each time you add a new patch? It would be easier to identify the new changes coming in.

@paperchalice
Copy link
Contributor Author

ping @aeubanks

vikramRH added a commit that referenced this pull request Jul 16, 2025
same as #138830

This partly solves the issue
#138831 for -enable-new-pm.

#137290 will not have this
problem, but this needs to be added this till we migrate to the new pass
builder structure.
Even with this, there is no way to -start-after an inserted pass right
now.

Co-authored-by : Oke, Akshat
<[[email protected]](mailto:[email protected])>
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Jul 16, 2025
…ions (#148111)

same as llvm/llvm-project#138830

This partly solves the issue
llvm/llvm-project#138831 for -enable-new-pm.

llvm/llvm-project#137290 will not have this
problem, but this needs to be added this till we migrate to the new pass
builder structure.
Even with this, there is no way to -start-after an inserted pass right
now.

Co-authored-by : Oke, Akshat
<[[email protected]](mailto:[email protected])>
@paperchalice
Copy link
Contributor Author

Ping? @aeubanks

@weliveindetail
Copy link
Member

If I understand correctly, the idea in this patch is that TargetPassBuilder mimics the interface of PassBuilder, but doesn't build the final structure of PassManagers yet. Instead it stores the the final PassInfoMixin instances in an intermediate structure of PassManagerWrappers and PassWrappers. Backends will derive from TargetPassBuilder and adjust this intermediate structure with operations like filtPassList() and injectBefore(). Eventually, TargetPassBuilder's constructRealPassManager() will transcribe it into the final structure of actual PassManagers and return the executable pipeline. (This part of the patch has no test coverage yet.) Is that about right?

@paperchalice
Copy link
Contributor Author

If I understand correctly, the idea in this patch is that TargetPassBuilder mimics the interface of PassBuilder, but doesn't build the final structure of PassManagers yet. Instead it stores the the final PassInfoMixin instances in an intermediate structure of PassManagerWrappers and PassWrappers. Backends will derive from TargetPassBuilder and adjust this intermediate structure with operations like filtPassList() and injectBefore(). Eventually, TargetPassBuilder's constructRealPassManager() will transcribe it into the final structure of actual PassManagers and return the executable pipeline. (This part of the patch has no test coverage yet.) Is that about right?

You are right. This is a dirty hack to implement --{start,stop}-{before,after} with following the design principle of new pass manager (users need to consider pass nesting explicitly, pass manager/builder should not handle this).

@weliveindetail
Copy link
Member

Thanks! The --{start,stop}-{before,after} feature seems very important for testing purposes. The RFC first proposed to drop it in favor of testing individual passes in isolation or batches of passes with intermediate serialization. This is what the optimization pipeline already uses with the new pass manager and we should bring the codegen pipeline in line with it.

However, the RFC also notes that this change to the testing strategy would involve a "huge amount of tedious work porting tests". This part of the proposal also received critical feedback from @arsenm and @akorobeynikov, who explain why it doesn't fit the nature of the codegen pipeline. I think their arguments are sounds and convincing.

Coming back to your patch: If we need an intermediate structure to provide this feature, it should be fine to have it. It might be worth to make it more explicit though. Perhaps consider it more as a planning mode for the PassBuilder, rather than hiding it behind a hack?

You are right. This is a dirty hack to implement --{start,stop}-{before,after} with following the design principle of new pass manager (users need to consider pass nesting explicitly, pass manager/builder should not handle this).

What exactly do you consider the hack here? The logic in constructRealPassManager() with respect to the explicit nesting requirement? Or do you mean the hijacking of the pass-managers for the intermediate structure?

@paperchalice
Copy link
Contributor Author

What exactly do you consider the hack here? The logic in constructRealPassManager() with respect to the explicit nesting requirement? Or do you mean the hijacking of the pass-managers for the intermediate structure?

Shadowing llvm::PassManager with llvm::TargetPassBuilder::PassManager, because I want to minimize the differences in usage between the two manager. Fortunately, it doesn't trigger warnings from -Wshadow.

Copy link
Member

@weliveindetail weliveindetail left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that the interface should be as close as possible, but matching the interface doesn't mean we have to fake the classes. Let's look at an example that might make the code simpler. It might also help reviewers :)


injectBefore<PreISelIntrinsicLoweringPass>([]() {
ModulePassManager MPM;
MPM.addPass(NoOpModulePass());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks very much like we use a llvm::PassManager<Module> while we actually get a llvm::TargetPassBuilder::PassManagerWrapper<llvm::PassManager<Module>, <more templates here>>. That might be surprising and figuring out the relations behind the scenes isn't exactly trivial :)

using FunctionPassManager =
PassManagerWrapper<llvm::FunctionPassManager, MachineFunctionPassManager>;
using ModulePassManager =
PassManagerWrapper<llvm::ModulePassManager, FunctionPassManager>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we just have a TargetModulePassManager instead? It wouldn't need to be hidden as a subclass anymore. And maybe even without templates? After all, PassManagerWrapper doesn't do so much.

is_detected<HasRunOnIRUnit, PassT, Function>::value;
template <typename PassT>
static constexpr bool isMachineFunctionPass =
is_detected<HasRunOnIRUnit, PassT, MachineFunction>::value;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then we could get rid of the type detection here (which btw doesn't work out-of-the-box on current main anymore)

std::conditional_t<isFunctionPass<PassT>, FunctionPassManager,
MachineFunctionPassManager>>>
void injectBefore(
typename llvm::identity<std::function<PassManagerT()>>::argument_type F) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And then.. maybe we could spell these out for the actual pass managers we have? This looks a lot simpler to me:

protected:
  template <typename PassT>
  void injectModulePassManagerBefore(std::function<ModulePassManager()> F) {
    injectCallback(PassT::name(), std::move(F));
  }

private:
  template <typename PassManagerT>
  void injectCallback(StringRef PassName, std::function<PassManagerT()> F) {
    InjectionCallbacks.push_back(
    ...

And the interface still seems acceptable:

injectModulePassManagerBefore<ExpandFpPass>([] {
  TargetModulePassManager MPM;
  MPM.addPass(NoOpModulePass());
  return MPM;
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need syntactic salt for pass pipeline building DSL here, i.e. let users know they are injecting module pass into function or machine function pass pipeline, module pass would cause higher memory usage in codegen pipeline.
Maybe we can make injectModulePassManagerBefore usable only when PassT is a function pass or machine function pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants