diff --git a/ffi/newpassmanagers.cpp b/ffi/newpassmanagers.cpp index 3643bac46..c5819e851 100644 --- a/ffi/newpassmanagers.cpp +++ b/ffi/newpassmanagers.cpp @@ -37,6 +37,10 @@ typedef OpaquePipelineTuningOptions *LLVMPipelineTuningOptionsRef; DEFINE_SIMPLE_CONVERSION_FUNCTIONS(PipelineTuningOptions, LLVMPipelineTuningOptionsRef) +struct OpaqueTimePassesHandler; +typedef OpaqueTimePassesHandler *LLVMTimePassesHandlerRef; +DEFINE_SIMPLE_CONVERSION_FUNCTIONS(TimePassesHandler, LLVMTimePassesHandlerRef) + static TargetMachine *unwrap(LLVMTargetMachineRef P) { return reinterpret_cast(P); } @@ -281,21 +285,57 @@ LLVMPY_DisposePipelineTuningOptions(LLVMPipelineTuningOptionsRef PTO) { // PB +API_EXPORT(LLVMTimePassesHandlerRef) +LLVMPY_CreateTimePassesHandler() { + bool enabled = true; + return llvm::wrap(new TimePassesHandler(enabled)); +} + +API_EXPORT(void) +LLVMPY_DisposeTimePassesHandler(LLVMTimePassesHandlerRef TimePassesRef) { + delete llvm::unwrap(TimePassesRef); +} + +API_EXPORT(void) +LLVMPY_EnableTimePasses(LLVMPassBuilderRef PBRef, + LLVMTimePassesHandlerRef TimePassesRef) { + TimePassesHandler *TP = llvm::unwrap(TimePassesRef); + TimePassesIsEnabled = true; + PassBuilder *PB = llvm::unwrap(PBRef); + PassInstrumentationCallbacks *PIC = PB->getPassInstrumentationCallbacks(); + TP->registerCallbacks(*PIC); +} + +API_EXPORT(void) +LLVMPY_ReportAndDisableTimePasses(LLVMTimePassesHandlerRef TimePassesRef, + const char **outmsg) { + std::string osbuf; + raw_string_ostream os(osbuf); + TimePassesHandler *TP = llvm::unwrap(TimePassesRef); + TP->setOutStream(os); + TP->print(); + os.flush(); + *outmsg = LLVMPY_CreateString(os.str().c_str()); + TimePassesIsEnabled = false; +} + API_EXPORT(LLVMPassBuilderRef) -LLVMPY_CreatePassBuilder(LLVMTargetMachineRef TM, - LLVMPipelineTuningOptionsRef PTO) { - TargetMachine *target = llvm::unwrap(TM); - PipelineTuningOptions *pt = llvm::unwrap(PTO); +LLVMPY_CreatePassBuilder(LLVMTargetMachineRef TMRef, + LLVMPipelineTuningOptionsRef PTORef) { + TargetMachine *TM = llvm::unwrap(TMRef); + PipelineTuningOptions *PTO = llvm::unwrap(PTORef); PassInstrumentationCallbacks *PIC = new PassInstrumentationCallbacks(); #if LLVM_VERSION_MAJOR < 16 - return llvm::wrap(new PassBuilder(target, *pt, None, PIC)); + return llvm::wrap(new PassBuilder(TM, *PTO, None, PIC)); #else - return llvm::wrap(new PassBuilder(target, *pt, std::nullopt, PIC)); + return llvm::wrap(new PassBuilder(TM, *PTO, std::nullopt, PIC)); #endif } API_EXPORT(void) -LLVMPY_DisposePassBuilder(LLVMPassBuilderRef PB) { delete llvm::unwrap(PB); } +LLVMPY_DisposePassBuilder(LLVMPassBuilderRef PBRef) { + delete llvm::unwrap(PBRef); +} static OptimizationLevel mapLevel(int speed_level, int size_level) { switch (size_level) { diff --git a/llvmlite/binding/ffi.py b/llvmlite/binding/ffi.py index 3464cb9c7..a0008ae44 100644 --- a/llvmlite/binding/ffi.py +++ b/llvmlite/binding/ffi.py @@ -40,7 +40,7 @@ def _make_opaque_ref(name): LLVMSectionIteratorRef = _make_opaque_ref("LLVMSectionIterator") LLVMOrcLLJITRef = _make_opaque_ref("LLVMOrcLLJITRef") LLVMOrcDylibTrackerRef = _make_opaque_ref("LLVMOrcDylibTrackerRef") - +LLVMTimePassesHandlerRef = _make_opaque_ref("LLVMTimePassesHandler") LLVMPipelineTuningOptionsRef = _make_opaque_ref("LLVMPipeLineTuningOptions") LLVMModulePassManagerRef = _make_opaque_ref("LLVMModulePassManager") LLVMFunctionPassManagerRef = _make_opaque_ref("LLVMFunctionPassManager") diff --git a/llvmlite/binding/newpassmanagers.py b/llvmlite/binding/newpassmanagers.py index c7082965e..9a7d799c3 100644 --- a/llvmlite/binding/newpassmanagers.py +++ b/llvmlite/binding/newpassmanagers.py @@ -1,4 +1,4 @@ -from ctypes import c_bool, c_int, c_size_t +from ctypes import c_bool, c_int, c_size_t, c_char_p, POINTER from enum import IntFlag from llvmlite.binding import ffi @@ -208,12 +208,21 @@ def _dispose(self): ffi.lib.LLVMPY_DisposePipelineTuningOptions(self) +class TimePassesHandler(ffi.ObjectRef): + def __init__(self): + super().__init__(ffi.lib.LLVMPY_CreateTimePassesHandler()) + + def _dispose(self): + ffi.lib.LLVMPY_DisposeTimePassesHandler(self) + + class PassBuilder(ffi.ObjectRef): def __init__(self, tm, pto): super().__init__(ffi.lib.LLVMPY_CreatePassBuilder(tm, pto)) self._pto = pto self._tm = tm + self._time_passes_handler = None def getModulePassManager(self): return ModulePassManager( @@ -227,6 +236,39 @@ def getFunctionPassManager(self): self, self._pto.speed_level, self._pto.size_level) ) + def start_pass_timing(self): + """Enable the pass timers. + + Raises + ------ + RuntimeError + If pass timing is already enabled. + """ + if self._time_passes_handler: + raise RuntimeError("Pass builder should only have one \ + pass timer at a time") + self._time_passes_handler = TimePassesHandler() + ffi.lib.LLVMPY_EnableTimePasses(self, self._time_passes_handler) + + def finish_pass_timing(self): + """Returns the pass timings report and disables the LLVM internal + timers. Pass timers are enabled by ``start_pass_timing()``. If the + timers are not enabled, this function will return an empty string. + + Returns + ------- + res : str + LLVM generated timing report. + """ + + if not self._time_passes_handler: + raise RuntimeError("Pass timing is not enabled") + + with ffi.OutputString() as buf: + ffi.lib.LLVMPY_ReportAndDisableTimePasses( + self._time_passes_handler, buf) + return str(buf) + def _dispose(self): ffi.lib.LLVMPY_DisposePassBuilder(self) @@ -339,11 +381,29 @@ def _dispose(self): # PassBuilder ffi.lib.LLVMPY_CreatePassBuilder.restype = ffi.LLVMPassBuilderRef -ffi.lib.LLVMPY_CreatePassBuilder.argtypes = [ffi.LLVMTargetMachineRef, - ffi.LLVMPipelineTuningOptionsRef,] +ffi.lib.LLVMPY_CreatePassBuilder.argtypes = [ + ffi.LLVMTargetMachineRef, + ffi.LLVMPipelineTuningOptionsRef, +] ffi.lib.LLVMPY_DisposePassBuilder.argtypes = [ffi.LLVMPassBuilderRef,] +ffi.lib.LLVMPY_CreateTimePassesHandler.restype = \ + ffi.LLVMTimePassesHandlerRef + +ffi.lib.LLVMPY_DisposeTimePassesHandler.argtypes = [ + ffi.LLVMTimePassesHandlerRef,] + +ffi.lib.LLVMPY_EnableTimePasses.argtypes = [ + ffi.LLVMPassBuilderRef, + ffi.LLVMTimePassesHandlerRef, +] + +ffi.lib.LLVMPY_ReportAndDisableTimePasses.argtypes = [ + ffi.LLVMTimePassesHandlerRef, + POINTER(c_char_p), +] + # Pipeline builders ffi.lib.LLVMPY_buildPerModuleDefaultPipeline.restype = \ diff --git a/llvmlite/tests/test_binding.py b/llvmlite/tests/test_binding.py index 6e9ab945b..523b35aee 100644 --- a/llvmlite/tests/test_binding.py +++ b/llvmlite/tests/test_binding.py @@ -3066,6 +3066,56 @@ def test_get_function_pass_manager(self): fpm.run(self.module().get_function("sum"), pb) pb.close() + def test_time_passes(self): + """Test pass timing reports for O3 and O0 optimization levels""" + def run_with_timing(speed_level): + mod = self.module() + pb = self.pb(speed_level=speed_level, size_level=0) + pb.start_pass_timing() + mpm = pb.getModulePassManager() + mpm.run(mod, pb) + report = pb.finish_pass_timing() + pb.close() + return report + + report_O3 = run_with_timing(3) + report_O0 = run_with_timing(0) + + self.assertIsInstance(report_O3, str) + self.assertIsInstance(report_O0, str) + self.assertEqual(report_O3.count("Pass execution timing report"), 1) + self.assertEqual(report_O0.count("Pass execution timing report"), 1) + + def test_empty_report(self): + mod = self.module() + pb = self.pb() + mpm = pb.getModulePassManager() + mpm.run(mod, pb) + pb.start_pass_timing() + report = pb.finish_pass_timing() + pb.close() + self.assertFalse(report) + + def test_multiple_timers_error(self): + mod = self.module() + pb = self.pb() + pb.start_pass_timing() + mpm = pb.getModulePassManager() + mpm.run(mod, pb) + pb.finish_pass_timing() + with self.assertRaises(RuntimeError): + pb.start_pass_timing() + pb.close() + + def test_empty_report_error(self): + mod = self.module() + pb = self.pb() + mpm = pb.getModulePassManager() + mpm.run(mod, pb) + with self.assertRaises(RuntimeError): + pb.finish_pass_timing() + pb.close() + class TestNewModulePassManager(BaseTest, NewPassManagerMixin): def pm(self):