Skip to content

Commit 21f5f32

Browse files
authored
[PassBuilder] Add callback invoking to PassBuilder string API (#157153)
This is a very rough state of what this can look like, but I didn't want to spend too much time on what could be a dead end. Currently the only way to invoke callbacks is by using the default pipelines, this is an issue if you want to define your own pipeline using the C string API (we do that in LLVM.jl in julia) so I extended the api to allow for invoking those callbacks just like one would call a pass of that kind. There are some questions about the params that these callbacks take and also I'm missing some of them (some of them are also invoked by the backend so we may not want to expose them) Code written with AI help, bugs are mine. (Not sure what policy for this is on LLVM)
1 parent 90d03a0 commit 21f5f32

File tree

3 files changed

+196
-21
lines changed

3 files changed

+196
-21
lines changed

llvm/lib/Passes/PassBuilder.cpp

Lines changed: 111 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@
189189
#include "llvm/Support/CodeGen.h"
190190
#include "llvm/Support/CommandLine.h"
191191
#include "llvm/Support/Debug.h"
192+
#include "llvm/Support/Error.h"
192193
#include "llvm/Support/ErrorHandling.h"
193194
#include "llvm/Support/FormatVariadic.h"
194195
#include "llvm/Support/Regex.h"
@@ -481,6 +482,26 @@ class RequireAllMachineFunctionPropertiesPass
481482

482483
} // namespace
483484

485+
static std::optional<OptimizationLevel> parseOptLevel(StringRef S) {
486+
return StringSwitch<std::optional<OptimizationLevel>>(S)
487+
.Case("O0", OptimizationLevel::O0)
488+
.Case("O1", OptimizationLevel::O1)
489+
.Case("O2", OptimizationLevel::O2)
490+
.Case("O3", OptimizationLevel::O3)
491+
.Case("Os", OptimizationLevel::Os)
492+
.Case("Oz", OptimizationLevel::Oz)
493+
.Default(std::nullopt);
494+
}
495+
496+
static Expected<OptimizationLevel> parseOptLevelParam(StringRef S) {
497+
std::optional<OptimizationLevel> OptLevel = parseOptLevel(S);
498+
if (OptLevel)
499+
return *OptLevel;
500+
return make_error<StringError>(
501+
formatv("invalid optimization level '{}'", S).str(),
502+
inconvertibleErrorCode());
503+
}
504+
484505
PassBuilder::PassBuilder(TargetMachine *TM, PipelineTuningOptions PTO,
485506
std::optional<PGOOptions> PGOOpt,
486507
PassInstrumentationCallbacks *PIC)
@@ -530,6 +551,96 @@ PassBuilder::PassBuilder(TargetMachine *TM, PipelineTuningOptions PTO,
530551
#include "llvm/Passes/MachinePassRegistry.def"
531552
});
532553
}
554+
555+
// Module-level callbacks without LTO phase
556+
registerPipelineParsingCallback(
557+
[this](StringRef Name, ModulePassManager &PM,
558+
ArrayRef<PassBuilder::PipelineElement>) {
559+
#define MODULE_CALLBACK(NAME, INVOKE) \
560+
if (PassBuilder::checkParametrizedPassName(Name, NAME)) { \
561+
auto L = PassBuilder::parsePassParameters(parseOptLevelParam, Name, NAME); \
562+
if (!L) { \
563+
errs() << NAME ": " << toString(L.takeError()) << '\n'; \
564+
return false; \
565+
} \
566+
INVOKE(PM, L.get()); \
567+
return true; \
568+
}
569+
#include "PassRegistry.def"
570+
return false;
571+
});
572+
573+
// Module-level callbacks with LTO phase (use Phase::None for string API)
574+
registerPipelineParsingCallback(
575+
[this](StringRef Name, ModulePassManager &PM,
576+
ArrayRef<PassBuilder::PipelineElement>) {
577+
#define MODULE_LTO_CALLBACK(NAME, INVOKE) \
578+
if (PassBuilder::checkParametrizedPassName(Name, NAME)) { \
579+
auto L = PassBuilder::parsePassParameters(parseOptLevelParam, Name, NAME); \
580+
if (!L) { \
581+
errs() << NAME ": " << toString(L.takeError()) << '\n'; \
582+
return false; \
583+
} \
584+
INVOKE(PM, L.get(), ThinOrFullLTOPhase::None); \
585+
return true; \
586+
}
587+
#include "PassRegistry.def"
588+
return false;
589+
});
590+
591+
// Function-level callbacks
592+
registerPipelineParsingCallback(
593+
[this](StringRef Name, FunctionPassManager &PM,
594+
ArrayRef<PassBuilder::PipelineElement>) {
595+
#define FUNCTION_CALLBACK(NAME, INVOKE) \
596+
if (PassBuilder::checkParametrizedPassName(Name, NAME)) { \
597+
auto L = PassBuilder::parsePassParameters(parseOptLevelParam, Name, NAME); \
598+
if (!L) { \
599+
errs() << NAME ": " << toString(L.takeError()) << '\n'; \
600+
return false; \
601+
} \
602+
INVOKE(PM, L.get()); \
603+
return true; \
604+
}
605+
#include "PassRegistry.def"
606+
return false;
607+
});
608+
609+
// CGSCC-level callbacks
610+
registerPipelineParsingCallback(
611+
[this](StringRef Name, CGSCCPassManager &PM,
612+
ArrayRef<PassBuilder::PipelineElement>) {
613+
#define CGSCC_CALLBACK(NAME, INVOKE) \
614+
if (PassBuilder::checkParametrizedPassName(Name, NAME)) { \
615+
auto L = PassBuilder::parsePassParameters(parseOptLevelParam, Name, NAME); \
616+
if (!L) { \
617+
errs() << NAME ": " << toString(L.takeError()) << '\n'; \
618+
return false; \
619+
} \
620+
INVOKE(PM, L.get()); \
621+
return true; \
622+
}
623+
#include "PassRegistry.def"
624+
return false;
625+
});
626+
627+
// Loop-level callbacks
628+
registerPipelineParsingCallback(
629+
[this](StringRef Name, LoopPassManager &PM,
630+
ArrayRef<PassBuilder::PipelineElement>) {
631+
#define LOOP_CALLBACK(NAME, INVOKE) \
632+
if (PassBuilder::checkParametrizedPassName(Name, NAME)) { \
633+
auto L = PassBuilder::parsePassParameters(parseOptLevelParam, Name, NAME); \
634+
if (!L) { \
635+
errs() << NAME ": " << toString(L.takeError()) << '\n'; \
636+
return false; \
637+
} \
638+
INVOKE(PM, L.get()); \
639+
return true; \
640+
}
641+
#include "PassRegistry.def"
642+
return false;
643+
});
533644
}
534645

535646
void PassBuilder::registerModuleAnalyses(ModuleAnalysisManager &MAM) {
@@ -615,26 +726,6 @@ static std::optional<int> parseDevirtPassName(StringRef Name) {
615726
return Count;
616727
}
617728

618-
static std::optional<OptimizationLevel> parseOptLevel(StringRef S) {
619-
return StringSwitch<std::optional<OptimizationLevel>>(S)
620-
.Case("O0", OptimizationLevel::O0)
621-
.Case("O1", OptimizationLevel::O1)
622-
.Case("O2", OptimizationLevel::O2)
623-
.Case("O3", OptimizationLevel::O3)
624-
.Case("Os", OptimizationLevel::Os)
625-
.Case("Oz", OptimizationLevel::Oz)
626-
.Default(std::nullopt);
627-
}
628-
629-
static Expected<OptimizationLevel> parseOptLevelParam(StringRef S) {
630-
std::optional<OptimizationLevel> OptLevel = parseOptLevel(S);
631-
if (OptLevel)
632-
return *OptLevel;
633-
return make_error<StringError>(
634-
formatv("invalid optimization level '{}'", S).str(),
635-
inconvertibleErrorCode());
636-
}
637-
638729
Expected<bool> PassBuilder::parseSinglePassOption(StringRef Params,
639730
StringRef OptionName,
640731
StringRef PassName) {

llvm/lib/Passes/PassRegistry.def

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ FUNCTION_PASS_WITH_PARAMS(
726726
return ExpandFpPass(TM, OL);
727727
},
728728
parseExpandFpOptions, "O0;O1;O2;O3")
729-
729+
730730
#undef FUNCTION_PASS_WITH_PARAMS
731731

732732
#ifndef LOOPNEST_PASS
@@ -805,3 +805,35 @@ LOOP_PASS_WITH_PARAMS(
805805
},
806806
parseLoopUnswitchOptions, "nontrivial;no-nontrivial;trivial;no-trivial")
807807
#undef LOOP_PASS_WITH_PARAMS
808+
809+
#ifdef MODULE_CALLBACK
810+
MODULE_CALLBACK("pipeline-start-callbacks", invokePipelineStartEPCallbacks)
811+
#endif
812+
#undef MODULE_CALLBACK
813+
814+
// There are some full lto specific ones that are ignored here for now
815+
#ifdef MODULE_LTO_CALLBACK
816+
MODULE_LTO_CALLBACK("pipeline-early-simplification-callbacks", invokePipelineEarlySimplificationEPCallbacks)
817+
MODULE_LTO_CALLBACK("optimizer-early-callbacks", invokeOptimizerEarlyEPCallbacks)
818+
MODULE_LTO_CALLBACK("optimizer-last-callbacks", invokeOptimizerLastEPCallbacks)
819+
#endif
820+
#undef MODULE_LTO_CALLBACK
821+
822+
#ifdef FUNCTION_CALLBACK
823+
FUNCTION_CALLBACK("peephole-callbacks", invokePeepholeEPCallbacks)
824+
FUNCTION_CALLBACK("scalar-optimizer-late-callbacks", invokeScalarOptimizerLateEPCallbacks)
825+
FUNCTION_CALLBACK("vectorizer-start-callbacks", invokeVectorizerStartEPCallbacks)
826+
FUNCTION_CALLBACK("vectorizer-end-callbacks", invokeVectorizerEndEPCallbacks)
827+
#endif
828+
#undef FUNCTION_CALLBACK
829+
830+
#ifdef CGSCC_CALLBACK
831+
CGSCC_CALLBACK("cgscc-optimizer-late-callbacks", invokeCGSCCOptimizerLateEPCallbacks)
832+
#endif
833+
#undef CGSCC_CALLBACK
834+
835+
#ifdef LOOP_CALLBACK
836+
LOOP_CALLBACK("late-loop-optimizations-callbacks", invokeLateLoopOptimizationsEPCallbacks)
837+
LOOP_CALLBACK("loop-optimizer-end-callbacks", invokeLoopOptimizerEndEPCallbacks)
838+
#endif
839+
#undef LOOP_CALLBACK
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
; RUN: opt -disable-output -print-pipeline-passes \
2+
; RUN: -passes-ep-vectorizer-start='no-op-function' \
3+
; RUN: -passes='function(vectorizer-start-callbacks<O3>)' < %s 2>&1 | FileCheck %s --check-prefix=VECSTART
4+
; RUN: opt -disable-output -print-pipeline-passes \
5+
; RUN: -passes-ep-peephole='no-op-function' \
6+
; RUN: -passes='peephole-callbacks<Os>' < %s 2>&1 | FileCheck %s --check-prefix=PEEP
7+
; RUN: opt -disable-output -print-pipeline-passes \
8+
; RUN: -passes-ep-pipeline-start='no-op-module' \
9+
; RUN: -passes='pipeline-start-callbacks<O1>' < %s 2>&1 | FileCheck %s --check-prefix=MODSTART
10+
; RUN: opt -disable-output -print-pipeline-passes \
11+
; RUN: -passes-ep-pipeline-early-simplification='no-op-module' \
12+
; RUN: -passes='pipeline-early-simplification-callbacks<O2>' < %s 2>&1 | FileCheck %s --check-prefix=LTOEARLY
13+
; RUN: opt -disable-output -print-pipeline-passes \
14+
; RUN: -passes-ep-optimizer-early='no-op-module' \
15+
; RUN: -passes='optimizer-early-callbacks<O2>' < %s 2>&1 | FileCheck %s --check-prefix=OPTEARLY
16+
; RUN: opt -disable-output -print-pipeline-passes \
17+
; RUN: -passes-ep-optimizer-last='no-op-module' \
18+
; RUN: -passes='optimizer-last-callbacks<O2>' < %s 2>&1 | FileCheck %s --check-prefix=OPTLAST
19+
; RUN: opt -disable-output -print-pipeline-passes \
20+
; RUN: -passes-ep-scalar-optimizer-late='no-op-function' \
21+
; RUN: -passes='function(scalar-optimizer-late-callbacks<O2>)' < %s 2>&1 | FileCheck %s --check-prefix=SCALATE
22+
; RUN: opt -disable-output -print-pipeline-passes \
23+
; RUN: -passes-ep-vectorizer-end='no-op-function' \
24+
; RUN: -passes='function(vectorizer-end-callbacks<O3>)' < %s 2>&1 | FileCheck %s --check-prefix=VECEND
25+
; RUN: opt -disable-output -print-pipeline-passes \
26+
; RUN: -passes-ep-late-loop-optimizations='no-op-loop' \
27+
; RUN: -passes='loop(late-loop-optimizations-callbacks<O2>)' < %s 2>&1 | FileCheck %s --check-prefix=LATELOOP
28+
; RUN: opt -disable-output -print-pipeline-passes \
29+
; RUN: -passes-ep-loop-optimizer-end='no-op-loop' \
30+
; RUN: -passes='loop(loop-optimizer-end-callbacks<O2>)' < %s 2>&1 | FileCheck %s --check-prefix=LOOPOPTEND
31+
; RUN: opt -disable-output -print-pipeline-passes \
32+
; RUN: -passes-ep-cgscc-optimizer-late='no-op-cgscc' \
33+
; RUN: -passes='cgscc(cgscc-optimizer-late-callbacks<O2>)' < %s 2>&1 | FileCheck %s --check-prefix=CGSCCTLATE
34+
; RUN: not opt -disable-output -passes='vectorizer-start-callbacks<foo>' < %s 2>&1 | FileCheck %s --check-prefix=INVALID
35+
36+
; VECSTART: no-op-function
37+
; PEEP: no-op-function
38+
; MODSTART: no-op-module
39+
; LTOEARLY: no-op-module
40+
; OPTEARLY: no-op-module
41+
; OPTLAST: no-op-module
42+
; SCALATE: no-op-function
43+
; VECEND: no-op-function
44+
; LATELOOP: no-op-loop
45+
; LOOPOPTEND: no-op-loop
46+
; CGSCCTLATE: no-op-cgscc
47+
; INVALID: invalid optimization level 'foo'
48+
49+
define void @f() {
50+
entry:
51+
ret void
52+
}

0 commit comments

Comments
 (0)