llvm-project icon indicating copy to clipboard operation
llvm-project copied to clipboard

[CodeGen][Pass] Add `TargetPassBuilder`

Open paperchalice opened this issue 7 months ago • 6 comments

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 avatar Apr 25 '25 07:04 paperchalice

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.

optimisan avatar Apr 29 '25 08:04 optimisan

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 avatar Apr 29 '25 10:04 paperchalice

Ping @aeubanks @arsenm

paperchalice avatar May 05 '25 12:05 paperchalice

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

paperchalice avatar May 08 '25 07:05 paperchalice

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.

cdevadas avatar May 08 '25 07:05 cdevadas

ping @aeubanks

paperchalice avatar Jun 30 '25 12:06 paperchalice

Ping? @aeubanks

paperchalice avatar Oct 30 '25 12:10 paperchalice

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?

weliveindetail avatar Nov 03 '25 11:11 weliveindetail

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).

paperchalice avatar Nov 03 '25 12:11 paperchalice

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?

weliveindetail avatar Nov 04 '25 12:11 weliveindetail

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.

paperchalice avatar Nov 04 '25 13:11 paperchalice

@paperchalice Following up from your inline comment

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.

Yes, I understand this is what the RFC means with explicit nesting. I wonder if we really need a DSL for it. Maybe a simple API, documentation and assertions would do :)

weliveindetail avatar Nov 12 '25 08:11 weliveindetail

Now CodeGen part are using pass managers with Target prefix.

paperchalice avatar Nov 13 '25 12:11 paperchalice