Unwanted execution of pass

Dear all,

since updating our downstream LLVM with the changes from llvm-project made since may 2021, compiling code for our target crashes, since a pass which was previously executed much later is now executed earlier. I do not know how to figure out why this is the case, so I would like to ask for hints on this.

Previously, the relevant part of the pass structure was as follows:

     ...
     Call Graph SCC Pass Manager
       Function Integration/Inlining
       FunctionPass Manager
         Dominator Tree Construction
         Natural Loop Information
         Scalar Evolution Analysis
         SPU Arithmetic Instruction Validator
         SPU offloading function dumper
         Module Verifier
         Lower Garbage Collection Instructions
         Shadow Stack GC Lowering
         Lower constant intrinsics
         Remove unreachable blocks from the CFG
         Expand vector predication intrinsics
         Scalarize Masked Memory Intrinsics
         Expand reduction intrinsics
     Rewrite Symbols
     FunctionPass Manager
       Exception handling preparation
       Safe Stack instrumentation pass
       Insert stack protectors
       Module Verifier
       Dominator Tree Construction
       Basic Alias Analysis (stateless AA impl)
       Function Alias Analysis Results
       Natural Loop Information
       Post-Dominator Tree Construction
       Branch Probability Analysis
       Lazy Branch Probability Analysis
       Lazy Block Frequency Analysis
       SPU DAG->DAG Pattern Instruction Selection (X)
     ...

The pass marked with (X) is added via PassConfig::addInstSelector() and is a MachineFunctionPass that depends on the fact that for all functions, inlining has been executed successfully.

With the latest changes from llvm-project, the pass structure looks as follows:

     ...
     Call Graph SCC Pass Manager
       Function Integration/Inlining
       FunctionPass Manager
         Dominator Tree Construction
         Basic Alias Analysis (stateless AA impl)
         Function Alias Analysis Results
         Natural Loop Information
         Lazy Branch Probability Analysis
         Lazy Block Frequency Analysis
         Optimization Remark Emitter
         Combine redundant instructions
         Simplify the CFG
         Dominator Tree Construction
         Natural Loop Information
         Canonicalize natural loops
         LCSSA Verifier
         Loop-Closed SSA Form Pass
         Basic Alias Analysis (stateless AA impl)
         Function Alias Analysis Results
         Scalar Evolution Analysis
         Loop Pass Manager
           Induction Variable Simplification
         Simplify the CFG
         Dead Code Elimination
         Dominator Tree Construction
         Natural Loop Information
         Scalar Evolution Analysis
         SPU IR Loop Info Extractor
         Dominator Tree Construction
         Natural Loop Information
         Scalar Evolution Analysis
         SPU Arithmetic Instruction Validator
         SPU offloading function dumper
         Module Verifier
         Lower Garbage Collection Instructions
         Shadow Stack GC Lowering
         Lower constant intrinsics
         Remove unreachable blocks from the CFG
         Expand vector predication intrinsics
         Scalarize Masked Memory Intrinsics
         Expand reduction intrinsics
         Exception handling preparation
         Safe Stack instrumentation pass
         Insert stack protectors
         Module Verifier
         Basic Alias Analysis (stateless AA impl)
         Function Alias Analysis Results
         Natural Loop Information
         Post-Dominator Tree Construction
         Branch Probability Analysis
         Lazy Branch Probability Analysis
         Lazy Block Frequency Analysis
         SPU DAG->DAG Pattern Instruction Selection (X)
     ...

Notice that in contrast to before, looking at the indentation of the passes, a lot more passes are now executed in the "FunctionPass Manager" execution directly following "Function Integration/Inlining". Especially, our (X) marked pass is now executed there.

I debugged into the execution of the passes. Previously, inlining was run for a certain function A, which is called from B, and then for B which successfully inlined A into B. Only after that, (X) was executed. Now, inlining is run for A, and before inlining is run for B, our (X) pass is run so that a crash results.

I would like to know why the structure changed, or how I can find out exactly what causes the structure to be as it is, e.g., find out why a certain pass is scheduled. I know that in principle this is because of the analysis usage of the passes, but this did not change for our passes so I am lost in finding out what happens here, because I don't understand how the analysis usage of the many standard LLVM passes we did not touch leads to the given structure at runtime.

Any help would be greatly appreciated.

Best regards,

Kai

That would be due to https://reviews.llvm.org/D99707.
I’m not sure how that pipeline worked for you before. There should be a separation between the optimization and the codegen pipeline or else we’d be doing codegen passes while IR passes will run later. For an example see https://github.com/llvm/llvm-project/blob/a767ae2c5ce7615c188baabd3b6a52bb880de234/clang/lib/CodeGen/BackendUtil.cpp#L986 where the two pipelines are in two separate pass managers.

With the new pass manager, the two pipelines can’t be in the same pass manager which makes the problem go away (and the new pass manager makes this sort of nesting explicit and thus harder to mess up even if we ever do get the codegen pipeline to run on the new pass manager).