[llvm] Reapply "[IR][NFC] Drop vtable from PassConcept/PassModel" (#208168) (PR #209414)

via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 14 02:33:09 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-ir

@llvm/pr-subscribers-llvm-transforms

Author: Alexis Engelke (aengelke)

<details>
<summary>Changes</summary>

The PassConcept/PassModel vtable has a size of 64 bytes (offset to top,
RTTI pointer, complete object destructor, deleting destructor, run,
printPipeline, name, isRequired), which add up to 53kiB (all
targets)/44 kiB (single-target). As more back-end passes get ported to
the new pass manager, this size will increase.

Remove the vtables by replacing the virtual dispatch with explicit
function pointers, initialized when adding the pass to the pass manager.
While this very slightly increases the cost of adding a pass, there's
also a very slight win from avoiding the vtable indirection when
running/destructing the pass.

Use a unique_ptr with a custom deleter to store PassConcept instances.
This avoids new/delete size mismatches.

This reverts commit 6ae5965f26e53d7338a779a73725a597a4d136e5.


---
Full diff: https://github.com/llvm/llvm-project/pull/209414.diff


5 Files Affected:

- (modified) llvm/include/llvm/Analysis/CGSCCPassManager.h (+9-9) 
- (modified) llvm/include/llvm/CodeGen/MachinePassManager.h (+3-4) 
- (modified) llvm/include/llvm/IR/PassManager.h (+5-5) 
- (modified) llvm/include/llvm/IR/PassManagerInternal.h (+79-36) 
- (modified) llvm/include/llvm/Transforms/Scalar/LoopPassManager.h (+9-9) 


``````````diff
diff --git a/llvm/include/llvm/Analysis/CGSCCPassManager.h b/llvm/include/llvm/Analysis/CGSCCPassManager.h
index e85c1ef9a90d2..1cfe68ee6fa59 100644
--- a/llvm/include/llvm/Analysis/CGSCCPassManager.h
+++ b/llvm/include/llvm/Analysis/CGSCCPassManager.h
@@ -319,7 +319,7 @@ class ModuleToPostOrderCGSCCPassAdaptor
       detail::PassConcept<LazyCallGraph::SCC, CGSCCAnalysisManager,
                           LazyCallGraph &, CGSCCUpdateResult &>;
 
-  explicit ModuleToPostOrderCGSCCPassAdaptor(std::unique_ptr<PassConceptT> Pass)
+  explicit ModuleToPostOrderCGSCCPassAdaptor(PassConceptT::unique_ptr Pass)
       : Pass(std::move(Pass)) {}
 
   ModuleToPostOrderCGSCCPassAdaptor(ModuleToPostOrderCGSCCPassAdaptor &&Arg)
@@ -347,7 +347,7 @@ class ModuleToPostOrderCGSCCPassAdaptor
   }
 
 private:
-  std::unique_ptr<PassConceptT> Pass;
+  PassConceptT::unique_ptr Pass;
 };
 
 /// A function to deduce a function pass type and wrap it in the
@@ -361,7 +361,7 @@ createModuleToPostOrderCGSCCPassAdaptor(CGSCCPassT &&Pass) {
   // Do not use make_unique, it causes too many template instantiations,
   // causing terrible compile times.
   return ModuleToPostOrderCGSCCPassAdaptor(
-      std::unique_ptr<ModuleToPostOrderCGSCCPassAdaptor::PassConceptT>(
+      ModuleToPostOrderCGSCCPassAdaptor::PassConceptT::unique_ptr(
           new PassModelT(std::forward<CGSCCPassT>(Pass))));
 }
 
@@ -447,7 +447,7 @@ class CGSCCToFunctionPassAdaptor
 public:
   using PassConceptT = detail::PassConcept<Function, FunctionAnalysisManager>;
 
-  explicit CGSCCToFunctionPassAdaptor(std::unique_ptr<PassConceptT> Pass,
+  explicit CGSCCToFunctionPassAdaptor(typename PassConceptT::unique_ptr Pass,
                                       bool EagerlyInvalidate, bool NoRerun)
       : Pass(std::move(Pass)), EagerlyInvalidate(EagerlyInvalidate),
         NoRerun(NoRerun) {}
@@ -490,7 +490,7 @@ class CGSCCToFunctionPassAdaptor
   }
 
 private:
-  std::unique_ptr<PassConceptT> Pass;
+  PassConceptT::unique_ptr Pass;
   bool EagerlyInvalidate;
   bool NoRerun;
 };
@@ -507,7 +507,7 @@ createCGSCCToFunctionPassAdaptor(FunctionPassT &&Pass,
   // Do not use make_unique, it causes too many template instantiations,
   // causing terrible compile times.
   return CGSCCToFunctionPassAdaptor(
-      std::unique_ptr<CGSCCToFunctionPassAdaptor::PassConceptT>(
+      CGSCCToFunctionPassAdaptor::PassConceptT::unique_ptr(
           new PassModelT(std::forward<FunctionPassT>(Pass))),
       EagerlyInvalidate, NoRerun);
 }
@@ -547,7 +547,7 @@ class DevirtSCCRepeatedPass
       detail::PassConcept<LazyCallGraph::SCC, CGSCCAnalysisManager,
                           LazyCallGraph &, CGSCCUpdateResult &>;
 
-  explicit DevirtSCCRepeatedPass(std::unique_ptr<PassConceptT> Pass,
+  explicit DevirtSCCRepeatedPass(PassConceptT::unique_ptr Pass,
                                  int MaxIterations)
       : Pass(std::move(Pass)), MaxIterations(MaxIterations) {}
 
@@ -565,7 +565,7 @@ class DevirtSCCRepeatedPass
   }
 
 private:
-  std::unique_ptr<PassConceptT> Pass;
+  PassConceptT::unique_ptr Pass;
   int MaxIterations;
 };
 
@@ -580,7 +580,7 @@ DevirtSCCRepeatedPass createDevirtSCCRepeatedPass(CGSCCPassT &&Pass,
   // Do not use make_unique, it causes too many template instantiations,
   // causing terrible compile times.
   return DevirtSCCRepeatedPass(
-      std::unique_ptr<DevirtSCCRepeatedPass::PassConceptT>(
+      DevirtSCCRepeatedPass::PassConceptT::unique_ptr(
           new PassModelT(std::forward<CGSCCPassT>(Pass))),
       MaxIterations);
 }
diff --git a/llvm/include/llvm/CodeGen/MachinePassManager.h b/llvm/include/llvm/CodeGen/MachinePassManager.h
index 936e80e88192c..5de520fa550f8 100644
--- a/llvm/include/llvm/CodeGen/MachinePassManager.h
+++ b/llvm/include/llvm/CodeGen/MachinePassManager.h
@@ -194,8 +194,7 @@ class FunctionToMachineFunctionPassAdaptor
   using PassConceptT =
       detail::PassConcept<MachineFunction, MachineFunctionAnalysisManager>;
 
-  explicit FunctionToMachineFunctionPassAdaptor(
-      std::unique_ptr<PassConceptT> Pass)
+  explicit FunctionToMachineFunctionPassAdaptor(PassConceptT::unique_ptr Pass)
       : Pass(std::move(Pass)) {}
 
   /// Runs the function pass across every function in the function.
@@ -205,7 +204,7 @@ class FunctionToMachineFunctionPassAdaptor
                 function_ref<StringRef(StringRef)> MapClassName2PassName);
 
 private:
-  std::unique_ptr<PassConceptT> Pass;
+  PassConceptT::unique_ptr Pass;
 };
 
 template <typename MachineFunctionPassT>
@@ -216,7 +215,7 @@ createFunctionToMachineFunctionPassAdaptor(MachineFunctionPassT &&Pass) {
   // Do not use make_unique, it causes too many template instantiations,
   // causing terrible compile times.
   return FunctionToMachineFunctionPassAdaptor(
-      std::unique_ptr<FunctionToMachineFunctionPassAdaptor::PassConceptT>(
+      FunctionToMachineFunctionPassAdaptor::PassConceptT::unique_ptr(
           new PassModelT(std::forward<MachineFunctionPassT>(Pass))));
 }
 
diff --git a/llvm/include/llvm/IR/PassManager.h b/llvm/include/llvm/IR/PassManager.h
index 22e02853bc567..0c6dac6be8d31 100644
--- a/llvm/include/llvm/IR/PassManager.h
+++ b/llvm/include/llvm/IR/PassManager.h
@@ -222,7 +222,7 @@ class PassManager : public RequiredPassInfoMixin<
         detail::PassModel<IRUnitT, PassT, AnalysisManagerT, ExtraArgTs...>;
     // Do not use make_unique or emplace_back, they cause too many template
     // instantiations, causing terrible compile times.
-    Passes.push_back(std::unique_ptr<PassConceptT>(
+    Passes.push_back(typename PassConceptT::unique_ptr(
         new PassModelT(std::forward<PassT>(Pass))));
   }
 
@@ -245,7 +245,7 @@ class PassManager : public RequiredPassInfoMixin<
   using PassConceptT =
       detail::PassConcept<IRUnitT, AnalysisManagerT, ExtraArgTs...>;
 
-  std::vector<std::unique_ptr<PassConceptT>> Passes;
+  std::vector<typename PassConceptT::unique_ptr> Passes;
 };
 
 template <typename IRUnitT>
@@ -875,7 +875,7 @@ class ModuleToFunctionPassAdaptor
 public:
   using PassConceptT = detail::PassConcept<Function, FunctionAnalysisManager>;
 
-  explicit ModuleToFunctionPassAdaptor(std::unique_ptr<PassConceptT> Pass,
+  explicit ModuleToFunctionPassAdaptor(PassConceptT::unique_ptr Pass,
                                        bool EagerlyInvalidate)
       : Pass(std::move(Pass)), EagerlyInvalidate(EagerlyInvalidate) {}
 
@@ -886,7 +886,7 @@ class ModuleToFunctionPassAdaptor
                 function_ref<StringRef(StringRef)> MapClassName2PassName);
 
 private:
-  std::unique_ptr<PassConceptT> Pass;
+  PassConceptT::unique_ptr Pass;
   bool EagerlyInvalidate;
 };
 
@@ -901,7 +901,7 @@ createModuleToFunctionPassAdaptor(FunctionPassT &&Pass,
   // Do not use make_unique, it causes too many template instantiations,
   // causing terrible compile times.
   return ModuleToFunctionPassAdaptor(
-      std::unique_ptr<ModuleToFunctionPassAdaptor::PassConceptT>(
+      ModuleToFunctionPassAdaptor::PassConceptT::unique_ptr(
           new PassModelT(std::forward<FunctionPassT>(Pass))),
       EagerlyInvalidate);
 }
diff --git a/llvm/include/llvm/IR/PassManagerInternal.h b/llvm/include/llvm/IR/PassManagerInternal.h
index f894f91cf1783..d731bc56a2820 100644
--- a/llvm/include/llvm/IR/PassManagerInternal.h
+++ b/llvm/include/llvm/IR/PassManagerInternal.h
@@ -34,68 +34,111 @@ class PreservedAnalyses;
 // Implementation details of the pass manager interfaces.
 namespace detail {
 
-/// Template for the abstract base class used to dispatch
-/// polymorphically over pass objects.
+/// Template for the abstract base class used to dispatch over pass objects.
+/// This doesn't use virtual functions to avoid vtables, which cost a fair
+/// amount of storage that needs to be relocated in PIC builds and add an extra
+/// indirection on dispatch.
 template <typename IRUnitT, typename AnalysisManagerT, typename... ExtraArgTs>
-struct PassConcept {
-  PassConcept() = default;
+class PassConcept {
+private:
+  using DestroyTy = void (*)(PassConcept &);
+  using RunTy = PreservedAnalyses (*)(PassConcept &, IRUnitT &,
+                                      AnalysisManagerT &, ExtraArgTs...);
+  using PrintPipelineTy =
+      void (*)(PassConcept &, raw_ostream &,
+               function_ref<StringRef(StringRef)> MapClassName2PassName);
 
-  // Boiler plate necessary for the container of derived classes.
-  virtual ~PassConcept() = default;
+public:
+  struct Deleter {
+    void operator()(PassConcept *P) { P->Destroy(*P); }
+  };
+
+  using unique_ptr = std::unique_ptr<PassConcept, Deleter>;
+
+private:
+  StringRef Name;
+  bool IsRequired;
+
+  DestroyTy Destroy;
+  RunTy Run;
+  PrintPipelineTy PrintPipeline;
+
+protected:
+  PassConcept(StringRef Name, bool IsRequired, DestroyTy Destroy, RunTy Run,
+              PrintPipelineTy PrintPipeline)
+      : Name(Name), IsRequired(IsRequired), Destroy(Destroy), Run(Run),
+        PrintPipeline(PrintPipeline) {}
 
+  // Note: this is intentionally not public to catch uses of delete and
+  // unique_ptr<PassConcept>.
+  void operator delete(void *P) { ::operator delete(P); }
+
+public:
   // Passes are immovable.
   PassConcept(const PassConcept &) = delete;
   PassConcept &operator=(const PassConcept &) = delete;
 
-  /// The polymorphic API which runs the pass over a given IR entity.
-  ///
-  /// Note that actual pass object can omit the analysis manager argument if
-  /// desired. Also that the analysis manager may be null if there is no
-  /// analysis manager in the pass pipeline.
-  virtual PreservedAnalyses run(IRUnitT &IR, AnalysisManagerT &AM,
-                                ExtraArgTs... ExtraArgs) = 0;
-
-  virtual void
-  printPipeline(raw_ostream &OS,
-                function_ref<StringRef(StringRef)> MapClassName2PassName) = 0;
-  /// Polymorphic method to access the name of a pass.
-  virtual StringRef name() const = 0;
+  /// Run the pass.
+  PreservedAnalyses run(IRUnitT &IR, AnalysisManagerT &AM,
+                        ExtraArgTs... ExtraArgs) {
+    return Run(*this, IR, AM, std::forward<ExtraArgTs>(ExtraArgs)...);
+  }
+
+  void printPipeline(raw_ostream &OS,
+                     function_ref<StringRef(StringRef)> MapClassName2PassName) {
+    PrintPipeline(*this, OS, MapClassName2PassName);
+  }
 
-  /// Polymorphic method to let a pass optionally exempted from skipping by
+  /// Get name of a pass.
+  StringRef name() const { return Name; }
+
+  /// Indicate whether a pass can optionally be exempted from skipping by
   /// PassInstrumentation.
   /// To opt-in, pass should implement `static bool isRequired()`, or inherit
   /// from `RequiredPassInfoMixin` or `OptionalPassInfoMixin`.
   /// It's no-op to have `isRequired` always return false since that is the
   /// default.
-  virtual bool isRequired() const = 0;
+  bool isRequired() const { return IsRequired; }
 };
 
-/// A template wrapper used to implement the polymorphic API.
+/// A template wrapper used to implement PassConcept.
 ///
 /// Can be instantiated for any object which provides a \c run method accepting
-/// an \c IRUnitT& and an \c AnalysisManager<IRUnit>&. It requires the pass to
-/// be a copyable object.
+/// an \c IRUnitT& and an \c AnalysisManager<IRUnit>&.
 template <typename IRUnitT, typename PassT, typename AnalysisManagerT,
           typename... ExtraArgTs>
-struct PassModel final : PassConcept<IRUnitT, AnalysisManagerT, ExtraArgTs...> {
-  explicit PassModel(PassT Pass) : Pass(std::move(Pass)) {}
+class PassModel final
+    : public PassConcept<IRUnitT, AnalysisManagerT, ExtraArgTs...> {
+private:
+  using PassConceptT = PassConcept<IRUnitT, AnalysisManagerT, ExtraArgTs...>;
 
-  PreservedAnalyses run(IRUnitT &IR, AnalysisManagerT &AM,
-                        ExtraArgTs... ExtraArgs) override {
-    return Pass.run(IR, AM, ExtraArgs...);
+  PassT Pass;
+
+  static PassT &getPass(PassConceptT &Self) {
+    return static_cast<PassModel &>(Self).Pass;
   }
 
-  void printPipeline(
-      raw_ostream &OS,
-      function_ref<StringRef(StringRef)> MapClassName2PassName) override {
-    Pass.printPipeline(OS, MapClassName2PassName);
+  static void destroyImpl(PassConceptT &Self) {
+    delete static_cast<PassModel *>(&Self);
   }
 
-  StringRef name() const override { return PassT::name(); }
+  static PreservedAnalyses runImpl(PassConceptT &Self, IRUnitT &IR,
+                                   AnalysisManagerT &AM,
+                                   ExtraArgTs... ExtraArgs) {
+    return getPass(Self).run(IR, AM, ExtraArgs...);
+  }
 
-  bool isRequired() const override { return PassT::isRequired(); }
+  static void
+  printPipelineImpl(PassConceptT &Self, raw_ostream &OS,
+                    function_ref<StringRef(StringRef)> MapClassName2PassName) {
+    getPass(Self).printPipeline(OS, MapClassName2PassName);
+  }
 
-  PassT Pass;
+public:
+  explicit PassModel(PassT Pass)
+      : PassConceptT(PassT::name(), PassT::isRequired(), destroyImpl, runImpl,
+                     printPipelineImpl),
+        Pass(std::move(Pass)) {}
 };
 
 /// Abstract concept of an analysis result.
diff --git a/llvm/include/llvm/Transforms/Scalar/LoopPassManager.h b/llvm/include/llvm/Transforms/Scalar/LoopPassManager.h
index 973d8a3cb1008..e11af53a47142 100644
--- a/llvm/include/llvm/Transforms/Scalar/LoopPassManager.h
+++ b/llvm/include/llvm/Transforms/Scalar/LoopPassManager.h
@@ -113,7 +113,7 @@ class PassManager<Loop, LoopAnalysisManager, LoopStandardAnalysisResults &,
       IsLoopNestPass.push_back(false);
       // Do not use make_unique or emplace_back, they cause too many template
       // instantiations, causing terrible compile times.
-      LoopPasses.push_back(std::unique_ptr<LoopPassConceptT>(
+      LoopPasses.push_back(LoopPassConceptT::unique_ptr(
           new LoopPassModelT(std::forward<PassT>(Pass))));
     } else {
       using LoopNestPassModelT =
@@ -122,7 +122,7 @@ class PassManager<Loop, LoopAnalysisManager, LoopStandardAnalysisResults &,
       IsLoopNestPass.push_back(true);
       // Do not use make_unique or emplace_back, they cause too many template
       // instantiations, causing terrible compile times.
-      LoopNestPasses.push_back(std::unique_ptr<LoopNestPassConceptT>(
+      LoopNestPasses.push_back(LoopNestPassConceptT::unique_ptr(
           new LoopNestPassModelT(std::forward<PassT>(Pass))));
     }
   }
@@ -143,8 +143,8 @@ class PassManager<Loop, LoopAnalysisManager, LoopStandardAnalysisResults &,
   // BitVector that identifies whether the passes are loop passes or loop-nest
   // passes (true for loop-nest passes).
   BitVector IsLoopNestPass;
-  std::vector<std::unique_ptr<LoopPassConceptT>> LoopPasses;
-  std::vector<std::unique_ptr<LoopNestPassConceptT>> LoopNestPasses;
+  std::vector<LoopPassConceptT::unique_ptr> LoopPasses;
+  std::vector<LoopNestPassConceptT::unique_ptr> LoopNestPasses;
 
   /// Run either a loop pass or a loop-nest pass. Returns `std::nullopt` if
   /// PassInstrumentation's BeforePass returns false. Otherwise, returns the
@@ -400,7 +400,7 @@ class FunctionToLoopPassAdaptor
       detail::PassConcept<Loop, LoopAnalysisManager,
                           LoopStandardAnalysisResults &, LPMUpdater &>;
 
-  explicit FunctionToLoopPassAdaptor(std::unique_ptr<PassConceptT> Pass,
+  explicit FunctionToLoopPassAdaptor(PassConceptT::unique_ptr Pass,
                                      bool UseMemorySSA = false,
                                      bool LoopNestMode = false)
       : Pass(std::move(Pass)), UseMemorySSA(UseMemorySSA),
@@ -418,7 +418,7 @@ class FunctionToLoopPassAdaptor
   bool isLoopNestMode() const { return LoopNestMode; }
 
 private:
-  std::unique_ptr<PassConceptT> Pass;
+  PassConceptT::unique_ptr Pass;
 
   FunctionPassManager LoopCanonicalizationFPM;
 
@@ -443,7 +443,7 @@ createFunctionToLoopPassAdaptor(LoopPassT &&Pass, bool UseMemorySSA = false) {
     // Do not use make_unique, it causes too many template instantiations,
     // causing terrible compile times.
     return FunctionToLoopPassAdaptor(
-        std::unique_ptr<FunctionToLoopPassAdaptor::PassConceptT>(
+        FunctionToLoopPassAdaptor::PassConceptT::unique_ptr(
             new PassModelT(std::forward<LoopPassT>(Pass))),
         UseMemorySSA, false);
   } else {
@@ -455,7 +455,7 @@ createFunctionToLoopPassAdaptor(LoopPassT &&Pass, bool UseMemorySSA = false) {
     // Do not use make_unique, it causes too many template instantiations,
     // causing terrible compile times.
     return FunctionToLoopPassAdaptor(
-        std::unique_ptr<FunctionToLoopPassAdaptor::PassConceptT>(
+        FunctionToLoopPassAdaptor::PassConceptT::unique_ptr(
             new PassModelT(std::move(LPM))),
         UseMemorySSA, true);
   }
@@ -476,7 +476,7 @@ createFunctionToLoopPassAdaptor<LoopPassManager>(LoopPassManager &&LPM,
   // Do not use make_unique, it causes too many template instantiations,
   // causing terrible compile times.
   return FunctionToLoopPassAdaptor(
-      std::unique_ptr<FunctionToLoopPassAdaptor::PassConceptT>(
+      FunctionToLoopPassAdaptor::PassConceptT::unique_ptr(
           new PassModelT(std::move(LPM))),
       UseMemorySSA, LoopNestMode);
 }

``````````

</details>


https://github.com/llvm/llvm-project/pull/209414


More information about the llvm-commits mailing list