Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable pass timings reporting with NewPassManager #1163

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 62 additions & 8 deletions ffi/newpassmanagers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ typedef OpaquePipelineTuningOptions *LLVMPipelineTuningOptionsRef;
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(PipelineTuningOptions,
LLVMPipelineTuningOptionsRef)

struct OpaquePassInstrumentationCallbacks;
typedef OpaquePassInstrumentationCallbacks *LLVMPassInstrumentationCallbacksRef;
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(PassInstrumentationCallbacks,
LLVMPassInstrumentationCallbacksRef)

struct OpaqueTimePassesHandler;
typedef OpaqueTimePassesHandler *LLVMTimePassesHandlerRef;
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(TimePassesHandler, LLVMTimePassesHandlerRef)

static TargetMachine *unwrap(LLVMTargetMachineRef P) {
return reinterpret_cast<TargetMachine *>(P);
}
Expand Down Expand Up @@ -281,21 +290,66 @@ LLVMPY_DisposePipelineTuningOptions(LLVMPipelineTuningOptionsRef PTO) {

// PB

API_EXPORT(LLVMPassInstrumentationCallbacksRef)
LLVMPY_LLVMPassInstrumentationCallbacksCreate() {
return llvm::wrap(new PassInstrumentationCallbacks());
}

API_EXPORT(void)
LLVMPY_LLVMPassInstrumentationCallbacksDispose(
LLVMPassInstrumentationCallbacksRef PICRef) {
delete llvm::unwrap(PICRef);
}

API_EXPORT(LLVMTimePassesHandlerRef)
LLVMPY_CreateLLVMTimePassesHandler() {
return llvm::wrap(new TimePassesHandler(true));
}

API_EXPORT(void)
LLVMPY_DisposeLLVMTimePassesHandler(LLVMTimePassesHandlerRef TimePassesRef) {
delete llvm::unwrap(TimePassesRef);
}

API_EXPORT(void)
LLVMPY_SetTimePassesNPM(LLVMTimePassesHandlerRef TimePassesRef,
LLVMPassInstrumentationCallbacksRef PICRef) {
TimePassesHandler *TP = llvm::unwrap(TimePassesRef);
TimePassesIsEnabled = true;
TP->registerCallbacks(*llvm::unwrap(PICRef));
}

API_EXPORT(void)
LLVMPY_ReportAndResetTimingsNPM(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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some asymmetry here - do pass instrumentation callbacks need to be unregistered if we've disabled pass timing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. A unique PICallbacks object belongs to PassBuilder object and is used to do stuff like this(hooks to enable/disable pass timings) after the optimisation pipeline has been built.

}

API_EXPORT(LLVMPassBuilderRef)
LLVMPY_CreatePassBuilder(LLVMTargetMachineRef TM,
LLVMPipelineTuningOptionsRef PTO) {
TargetMachine *target = llvm::unwrap(TM);
PipelineTuningOptions *pt = llvm::unwrap(PTO);
PassInstrumentationCallbacks *PIC = new PassInstrumentationCallbacks();
LLVMPY_CreatePassBuilder(LLVMTargetMachineRef TMRef,
LLVMPipelineTuningOptionsRef PTORef,
LLVMPassInstrumentationCallbacksRef PICRef) {
TargetMachine *TM = llvm::unwrap(TMRef);
PipelineTuningOptions *PTO = llvm::unwrap(PTORef);
PassInstrumentationCallbacks *PIC = llvm::unwrap(PICRef);
#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) {
Expand Down
4 changes: 3 additions & 1 deletion llvmlite/binding/ffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ def _make_opaque_ref(name):
LLVMSectionIteratorRef = _make_opaque_ref("LLVMSectionIterator")
LLVMOrcLLJITRef = _make_opaque_ref("LLVMOrcLLJITRef")
LLVMOrcDylibTrackerRef = _make_opaque_ref("LLVMOrcDylibTrackerRef")

LLVMPassInstrumentationCallbacksRef = \
_make_opaque_ref("LLVMPassInstrumentationCallbacks")
LLVMTimePassesHandlerRef = _make_opaque_ref("LLVMTimePassesHandler")
LLVMPipelineTuningOptionsRef = _make_opaque_ref("LLVMPipeLineTuningOptions")
LLVMModulePassManagerRef = _make_opaque_ref("LLVMModulePassManager")
LLVMFunctionPassManagerRef = _make_opaque_ref("LLVMFunctionPassManager")
Expand Down
73 changes: 69 additions & 4 deletions llvmlite/binding/newpassmanagers.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -208,10 +208,30 @@ def _dispose(self):
ffi.lib.LLVMPY_DisposePipelineTuningOptions(self)


class TimePassesHandler(ffi.ObjectRef):
def __init__(self):
super().__init__(ffi.lib.LLVMPY_CreateLLVMTimePassesHandler())

def _dispose(self):
ffi.lib.LLVMPY_DisposeLLVMTimePassesHandler(self)


class PassInstrumentationCallbacks(ffi.ObjectRef):
def __init__(self):
super().__init__(
ffi.lib.LLVMPY_LLVMPassInstrumentationCallbacksCreate()
)

def _dispose(self):
ffi.lib.LLVMPY_LLVMPassInstrumentationCallbacksDispose(self)


class PassBuilder(ffi.ObjectRef):

def __init__(self, tm, pto):
super().__init__(ffi.lib.LLVMPY_CreatePassBuilder(tm, pto))
self._pic = PassInstrumentationCallbacks()
self._time_passes = TimePassesHandler()
super().__init__(ffi.lib.LLVMPY_CreatePassBuilder(tm, pto, self._pic))
self._pto = pto
self._tm = tm

Expand All @@ -227,6 +247,24 @@ def getFunctionPassManager(self):
self, self._pto.speed_level, self._pto.size_level)
)

def set_time_passes(self):
"""Enable the pass timers.
"""
ffi.lib.LLVMPY_SetTimePassesNPM(self._time_passes, self._pic)

def report_and_reset_timings(self):
"""Returns the pass timings report and resets the LLVM internal timers.
Pass timers are enabled by ``set_time_passes()``. If the timers are not
enabled, this function will return an empty string.
Returns
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this also disables pass timing.

-------
res : str
LLVM generated timing report.
"""
with ffi.OutputString() as buf:
ffi.lib.LLVMPY_ReportAndResetTimingsNPM(self._time_passes, buf)
return str(buf)

def _dispose(self):
ffi.lib.LLVMPY_DisposePassBuilder(self)

Expand Down Expand Up @@ -339,11 +377,38 @@ 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.LLVMPassInstrumentationCallbacksRef,
]

ffi.lib.LLVMPY_DisposePassBuilder.argtypes = [ffi.LLVMPassBuilderRef,]

ffi.lib.LLVMPY_CreateLLVMTimePassesHandler.restype = \
ffi.LLVMTimePassesHandlerRef

ffi.lib.LLVMPY_DisposeLLVMTimePassesHandler.argtypes = [
ffi.LLVMTimePassesHandlerRef,]

ffi.lib.LLVMPY_SetTimePassesNPM.argtypes = [
ffi.LLVMTimePassesHandlerRef,
ffi.LLVMPassInstrumentationCallbacksRef,
]

ffi.lib.LLVMPY_ReportAndResetTimingsNPM.argtypes = [
ffi.LLVMTimePassesHandlerRef,
POINTER(c_char_p),
]

ffi.lib.LLVMPY_LLVMPassInstrumentationCallbacksCreate.restype = (
ffi.LLVMPassInstrumentationCallbacksRef
)

ffi.lib.LLVMPY_LLVMPassInstrumentationCallbacksDispose.argtypes = (
ffi.LLVMPassInstrumentationCallbacksRef,
)

# Pipeline builders

ffi.lib.LLVMPY_buildPerModuleDefaultPipeline.restype = \
Expand Down
29 changes: 29 additions & 0 deletions llvmlite/tests/test_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -3066,6 +3066,35 @@ 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.set_time_passes()
mpm = pb.getModulePassManager()
mpm.run(mod, pb)
report = pb.report_and_reset_timings()
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)
report = pb.report_and_reset_timings()
pb.close()
self.assertFalse(report)


class TestNewModulePassManager(BaseTest, NewPassManagerMixin):
def pm(self):
Expand Down
Loading