from ctypes import c_bool, c_int, c_size_t, POINTER, Structure, byref, c_char_p
from collections import namedtuple
from enum import IntFlag
from llvmlite.binding import ffi


def create_new_module_pass_manager():
    return ModulePassManager()


def create_new_function_pass_manager():
    return FunctionPassManager()


def create_pass_builder(tm, pto):
    return PassBuilder(tm, pto)


def create_pipeline_tuning_options(speed_level=2, size_level=0):
    return PipelineTuningOptions(speed_level, size_level)


_prunestats = namedtuple('PruneStats',
                         ('basicblock diamond fanout fanout_raise'))


class PruneStats(_prunestats):
    """ Holds statistics from reference count pruning.
    """

    def __add__(self, other):
        if not isinstance(other, PruneStats):
            msg = 'PruneStats can only be added to another PruneStats, got {}.'
            raise TypeError(msg.format(type(other)))
        return PruneStats(self.basicblock + other.basicblock,
                          self.diamond + other.diamond,
                          self.fanout + other.fanout,
                          self.fanout_raise + other.fanout_raise)

    def __sub__(self, other):
        if not isinstance(other, PruneStats):
            msg = ('PruneStats can only be subtracted from another PruneStats, '
                   'got {}.')
            raise TypeError(msg.format(type(other)))
        return PruneStats(self.basicblock - other.basicblock,
                          self.diamond - other.diamond,
                          self.fanout - other.fanout,
                          self.fanout_raise - other.fanout_raise)


class _c_PruneStats(Structure):
    _fields_ = [
        ('basicblock', c_size_t),
        ('diamond', c_size_t),
        ('fanout', c_size_t),
        ('fanout_raise', c_size_t)]


def dump_refprune_stats(printout=False):
    """ Returns a namedtuple containing the current values for the refop pruning
    statistics. If kwarg `printout` is True the stats are printed to stderr,
    default is False.
    """

    stats = _c_PruneStats(0, 0, 0, 0)
    do_print = c_bool(printout)

    ffi.lib.LLVMPY_DumpRefPruneStats(byref(stats), do_print)
    return PruneStats(stats.basicblock, stats.diamond, stats.fanout,
                      stats.fanout_raise)


# TODO: Rename and add tests for these
# Although new pass manager has its own timing APIs, we still need to support
# the legacy ones as LLVM backend still used the LegacyPassManager. These APIs
# will be used to time the backend passes such as instruction selection,
# regalloc, etc
def set_time_passes(enable):
    """Enable or disable the pass timers.

    Parameters
    ----------
    enable : bool
        Set to True to enable the pass timers.
        Set to False to disable the pass timers.
    """
    ffi.lib.LLVMPY_SetTimePasses(c_bool(enable))


def report_and_reset_timings():
    """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
    -------
    res : str
        LLVM generated timing report.
    """
    with ffi.OutputString() as buf:
        ffi.lib.LLVMPY_ReportAndResetTimings(buf)
        return str(buf)


class RefPruneSubpasses(IntFlag):
    PER_BB       = 0b0001    # noqa: E221
    DIAMOND      = 0b0010    # noqa: E221
    FANOUT       = 0b0100    # noqa: E221
    FANOUT_RAISE = 0b1000
    ALL = PER_BB | DIAMOND | FANOUT | FANOUT_RAISE


class NewPassManager():

    def __init__(self):
        if type(self) is NewPassManager:
            raise TypeError("Cannot instantiate NewPassManager directly")

    def run(self,IR, pb):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_RunNewModulePassManager(self, IR, pb)
        else:
            ffi.lib.LLVMPY_RunNewFunctionPassManager(self, IR, pb)

    def add_aa_eval_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddAAEvaluator(self)
        else:
            ffi.lib.LLVMPY_function_AddAAEvaluator(self)

    def add_simplify_cfg_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddSimplifyCFGPass(self)
        else:
            ffi.lib.LLVMPY_function_AddSimplifyCFGPass(self)

    def add_loop_unroll_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLoopUnrollPass(self)
        else:
            ffi.lib.LLVMPY_function_AddLoopUnrollPass(self)

    def add_instruction_combine_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddInstCombinePass(self)
        else:
            ffi.lib.LLVMPY_function_AddInstCombinePass(self)

    def add_jump_threading_pass(self, threshold=-1):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_AddJumpThreadingPass_module(self, threshold)
        else:
            ffi.lib.LLVMPY_AddJumpThreadingPass_function(self, threshold)

    def add_cfg_printer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddCFGPrinterPass(self)
        else:
            ffi.lib.LLVMPY_function_AddCFGPrinterPass(self)

    def add_cfg_only_printer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddCFGOnlyPrinterPass(self)
        else:
            ffi.lib.LLVMPY_function_AddCFGOnlyPrinterPass(self)

    def add_dom_printer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddDomPrinter(self)
        else:
            ffi.lib.LLVMPY_function_AddDomPrinter(self)

    def add_dom_only_printer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddDomOnlyPrinter(self)
        else:
            ffi.lib.LLVMPY_function_AddDomOnlyPrinter(self)

    def add_post_dom_printer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddPostDomPrinter(self)
        else:
            ffi.lib.LLVMPY_function_AddPostDomPrinter(self)

    def add_post_dom_only_printer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddPostDomOnlyPrinter(self)
        else:
            ffi.lib.LLVMPY_function_AddPostDomOnlyPrinter(self)

    def add_dom_viewer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddDomViewer(self)
        else:
            ffi.lib.LLVMPY_function_AddDomViewer(self)

    def add_dom_only_viewer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddDomOnlyViewer(self)
        else:
            ffi.lib.LLVMPY_function_AddDomOnlyViewer(self)

    def add_post_dom_viewer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddPostDomViewer(self)
        else:
            ffi.lib.LLVMPY_function_AddPostDomViewer(self)

    def add_post_dom_only_viewer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddPostDomOnlyViewer(self)
        else:
            ffi.lib.LLVMPY_function_AddPostDomOnlyViewer(self)

    def add_lint_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLintPass(self)
        else:
            ffi.lib.LLVMPY_function_AddLintPass(self)

    def add_aggressive_dce_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddADCEPass(self)
        else:
            ffi.lib.LLVMPY_function_AddADCEPass(self)

    def add_break_critical_edges_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddBreakCriticalEdgesPass(self)
        else:
            ffi.lib.LLVMPY_function_AddBreakCriticalEdgesPass(self)

    def add_dead_store_elimination_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddDSEPass(self)
        else:
            ffi.lib.LLVMPY_function_AddDSEPass(self)

    def add_dead_code_elimination_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddDCEPass(self)
        else:
            ffi.lib.LLVMPY_function_AddDCEPass(self)

    def add_aggressive_instcombine_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddAggressiveInstCombinePass(self)
        else:
            ffi.lib.LLVMPY_function_AddAggressiveInstCombinePass(self)

    def add_lcssa_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLCSSAPass(self)
        else:
            ffi.lib.LLVMPY_function_AddLCSSAPass(self)

    def add_new_gvn_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddNewGVNPass(self)
        else:
            ffi.lib.LLVMPY_function_AddNewGVNPass(self)

    def add_loop_simplify_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLoopSimplifyPass(self)
        else:
            ffi.lib.LLVMPY_function_AddLoopSimplifyPass(self)

    def add_loop_unroll_and_jam_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLoopUnrollAndJamPass(self)
        else:
            ffi.lib.LLVMPY_function_AddLoopUnrollAndJamPass(self)

    def add_sccp_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddSCCPPass(self)
        else:
            ffi.lib.LLVMPY_function_AddSCCPPass(self)

    def add_lower_atomic_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLowerAtomicPass(self)
        else:
            ffi.lib.LLVMPY_function_AddLowerAtomicPass(self)

    def add_lower_invoke_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLowerInvokePass(self)
        else:
            ffi.lib.LLVMPY_function_AddLowerInvokePass(self)

    def add_lower_switch_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLowerSwitchPass(self)
        else:
            ffi.lib.LLVMPY_function_AddLowerSwitchPass(self)

    def add_mem_copy_opt_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddMemCpyOptPass(self)
        else:
            ffi.lib.LLVMPY_function_AddMemCpyOptPass(self)

    def add_unify_function_exit_nodes_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddUnifyFunctionExitNodesPass(self)
        else:
            ffi.lib.LLVMPY_function_AddUnifyFunctionExitNodesPass(self)

    def add_reassociate_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddReassociatePass(self)
        else:
            ffi.lib.LLVMPY_function_AddReassociatePass(self)

    def add_register_to_memory_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddRegToMemPass(self)
        else:
            ffi.lib.LLVMPY_function_AddRegToMemPass(self)

    def add_sroa_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddSROAPass(self)
        else:
            ffi.lib.LLVMPY_function_AddSROAPass(self)

    def add_sinking_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddSinkingPass(self)
        else:
            ffi.lib.LLVMPY_function_AddSinkingPass(self)

    def add_tail_call_elimination_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddTailCallElimPass(self)
        else:
            ffi.lib.LLVMPY_function_AddTailCallElimPass(self)

    def add_instruction_namer_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddInstructionNamerPass(self)
        else:
            ffi.lib.LLVMPY_function_AddInstructionNamerPass(self)

    def add_loop_deletion_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLoopDeletionPass(self)
        else:
            ffi.lib.LLVMPY_function_AddLoopDeletionPass(self)

    def add_loop_strength_reduce_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLoopStrengthReducePass(self)
        else:
            ffi.lib.LLVMPY_function_AddLoopStrengthReducePass(self)

    def add_loop_rotate_pass(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_module_AddLoopRotatePass(self)
        else:
            ffi.lib.LLVMPY_function_AddLoopRotatePass(self)

    def _dispose(self):
        if isinstance(self, ModulePassManager):
            ffi.lib.LLVMPY_DisposeNewModulePassManger(self)
        else:
            ffi.lib.LLVMPY_DisposeNewFunctionPassManger(self)


class ModulePassManager(ffi.ObjectRef, NewPassManager):

    def __init__(self, ptr=None):
        if ptr is None:
            ptr = ffi.lib.LLVMPY_CreateNewModulePassManager()
        super().__init__(ptr)

    def add_verifier(self):
        ffi.lib.LLVMPY_module_AddVerifierPass(self)

    def add_constant_merge_pass(self):
        ffi.lib.LLVMPY_module_AddConstantMergePass(self)

    def add_dead_arg_elimination_pass(self):
        ffi.lib.LLVMPY_module_AddDeadArgumentEliminationPass(self)

    def add_dot_call_graph_printer_pass(self):
        ffi.lib.LLVMPY_module_AddCallGraphDOTPrinterPass(self)

    # TODO: There are a lot more printer passes in llvm that can be exposed
    # FIXME: Find a way to write the output to a buffer instead of stdout
    def add_module_debug_info_pass(self):
        ffi.lib.LLVMPY_module_AddModuleDebugInfoPrinterPass(self)

    def add_always_inliner_pass(self):
        ffi.lib.LLVMPY_module_AddAlwaysInlinerPass(self)

    def add_rpo_function_attrs_pass(self):
        ffi.lib.LLVMPY_module_AddReversePostOrderFunctionAttrsPass(self)

    def add_global_dead_code_eliminate_pass(self):
        ffi.lib.LLVMPY_module_AddGlobalDCEPass(self)

    def add_global_opt_pass(self):
        ffi.lib.LLVMPY_module_AddGlobalOptPass(self)

    def add_ipsccp_pass(self):
        ffi.lib.LLVMPY_module_AddIPSCCPPass(self)

    def add_internalize_pass(self):
        ffi.lib.LLVMPY_module_AddInternalizePass(self)

    def add_loop_extract_pass(self):
        ffi.lib.LLVMPY_module_AddLoopExtractorPass(self)

    def add_merge_functions_pass(self):
        ffi.lib.LLVMPY_module_AddMergeFunctionsPass(self)

    def add_partial_inliner_pass(self):
        ffi.lib.LLVMPY_module_AddPartialInlinerPass(self)

    def add_strip_symbols_pass(self):
        ffi.lib.LLVMPY_module_AddStripSymbolsPass(self)

    def add_strip_dead_debug_info_pass(self):
        ffi.lib.LLVMPY_module_AddStripDeadDebugInfoPass(self)

    def add_strip_dead_prototype_pass(self):
        ffi.lib.LLVMPY_module_AddStripDeadPrototypesPass(self)

    def add_strip_debug_declare_pass(self):
        ffi.lib.LLVMPY_module_AddStripDebugDeclarePass(self)

    def add_strip_non_debug_symbols_pass(self):
        ffi.lib.LLVMPY_module_AddStripNonDebugSymbolsPass(self)

    def add_argument_promotion_pass(self):
        ffi.lib.LLVMPY_module_AddArgumentPromotionPass(self)

    def add_post_order_function_attributes_pass(self):
        ffi.lib.LLVMPY_module_AddPostOrderFunctionAttrsPass(self)

    # Non-standard LLVM passes
    def add_refprune_pass(self, subpasses_flags=RefPruneSubpasses.ALL,
                          subgraph_limit=1000):
        """Add Numba specific Reference count pruning pass.

        Parameters
        ----------
        subpasses_flags : RefPruneSubpasses
            A bitmask to control the subpasses to be enabled.
        subgraph_limit : int
            Limit the fanout pruners to working on a subgraph no bigger than
            this number of basic-blocks to avoid spending too much time in very
            large graphs. Default is 1000. Subject to change in future
            versions.
        """
        iflags = RefPruneSubpasses(subpasses_flags)
        ffi.lib.LLVMPY_AddRefPrunePass_module(self, iflags, subgraph_limit)


class FunctionPassManager(ffi.ObjectRef, NewPassManager):

    def __init__(self, ptr=None):
        if ptr is None:
            ptr = ffi.lib.LLVMPY_CreateNewFunctionPassManager()
        super().__init__(ptr)

    # Non-standard LLVM passes
    def add_refprune_pass(self, subpasses_flags=RefPruneSubpasses.ALL,
                          subgraph_limit=1000):
        """Add Numba specific Reference count pruning pass.

        Parameters
        ----------
        subpasses_flags : RefPruneSubpasses
            A bitmask to control the subpasses to be enabled.
        subgraph_limit : int
            Limit the fanout pruners to working on a subgraph no bigger than
            this number of basic-blocks to avoid spending too much time in very
            large graphs. Default is 1000. Subject to change in future
            versions.
        """
        iflags = RefPruneSubpasses(subpasses_flags)
        ffi.lib.LLVMPY_AddRefPrunePass_function(self, iflags, subgraph_limit)


class PipelineTuningOptions(ffi.ObjectRef):

    def __init__(self, speed_level=2, size_level=0):
        self._speed_level = None
        self._size_level = None
        self.speed_level = speed_level
        self.size_level = size_level
        super().__init__(ffi.lib.LLVMPY_CreatePipelineTuningOptions())

    @property
    def speed_level(self):
        return self._speed_level

    @speed_level.setter
    def speed_level(self, value):
        if not 0 <= value <= 3:
            raise ValueError(
                "Optimization level for speed should be 0, 1, 2, or 3")
        self._speed_level = value

    @property
    def size_level(self):
        return self._size_level

    @size_level.setter
    def size_level(self, value):
        if not 0 <= value <= 2:
            raise ValueError("Optimization level for size should be 0, 1, or 2")
        if value != 0 and self.speed_level != 2:
            raise ValueError(
                "Optimization for size should be encoded with speed level == 2")
        self._size_level = value

    @property
    def loop_interleaving(self):
        return ffi.lib.LLVMPY_PTOGetLoopInterleaving(self)

    @loop_interleaving.setter
    def loop_interleaving(self, value):
        ffi.lib.LLVMPY_PTOSetLoopInterleaving(self, value)

    @property
    def loop_vectorization(self):
        return ffi.lib.LLVMPY_PTOGetLoopVectorization(self)

    @loop_vectorization.setter
    def loop_vectorization(self, value):
        ffi.lib.LLVMPY_PTOSetLoopVectorization(self, value)

    @property
    def slp_vectorization(self):
        return ffi.lib.LLVMPY_PTOGetSLPVectorization(self)

    @slp_vectorization.setter
    def slp_vectorization(self, value):
        ffi.lib.LLVMPY_PTOSetSLPVectorization(self, value)

    @property
    def loop_unrolling(self):
        return ffi.lib.LLVMPY_PTOGetLoopUnrolling(self)

    @loop_unrolling.setter
    def loop_unrolling(self, value):
        ffi.lib.LLVMPY_PTOSetLoopUnrolling(self, value)

    @property
    def inlining_threshold(self):
        return ffi.lib.LLVMPY_PTOGetInlinerThreshold(self)

    @inlining_threshold.setter
    def inlining_threshold(self, value):
        ffi.lib.LLVMPY_PTOSetInlinerThreshold(self, value)

    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(
            ffi.lib.LLVMPY_buildPerModuleDefaultPipeline(
                self, self._pto.speed_level, self._pto.size_level)
        )

    def getFunctionPassManager(self):
        return FunctionPassManager(
            ffi.lib.LLVMPY_buildFunctionSimplificationPipeline(
                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 timing can only be done once")
        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)


# ============================================================================
# FFI

ffi.lib.LLVMPY_DumpRefPruneStats.argtypes = [POINTER(_c_PruneStats), c_bool]

ffi.lib.LLVMPY_SetTimePasses.argtypes = [c_bool]

ffi.lib.LLVMPY_ReportAndResetTimings.argtypes = [POINTER(c_char_p)]

# ModulePassManager

ffi.lib.LLVMPY_CreateNewModulePassManager.restype = ffi.LLVMModulePassManagerRef

ffi.lib.LLVMPY_RunNewModulePassManager.argtypes = [
    ffi.LLVMModulePassManagerRef, ffi.LLVMModuleRef,
    ffi.LLVMPassBuilderRef,]

ffi.lib.LLVMPY_module_AddVerifierPass.argtypes = [ffi.LLVMModulePassManagerRef,]
ffi.lib.LLVMPY_module_AddAAEvaluator.argtypes = [ffi.LLVMModulePassManagerRef,]
ffi.lib.LLVMPY_module_AddSimplifyCFGPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLoopUnrollPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLoopRotatePass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddInstCombinePass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_AddJumpThreadingPass_module.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddCFGPrinterPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddCFGOnlyPrinterPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddDomPrinter.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddDomOnlyPrinter.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddPostDomPrinter.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddPostDomOnlyPrinter.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddDomViewer.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddDomOnlyViewer.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddPostDomViewer.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddPostDomOnlyViewer.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLintPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddADCEPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddBreakCriticalEdgesPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddDSEPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddDCEPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddAggressiveInstCombinePass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLCSSAPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddNewGVNPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLoopSimplifyPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLoopUnrollAndJamPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddSCCPPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLowerAtomicPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLowerInvokePass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLowerSwitchPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddMemCpyOptPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddUnifyFunctionExitNodesPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddReassociatePass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddRegToMemPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddSROAPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddSinkingPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddTailCallElimPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddInstructionNamerPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLoopDeletionPass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddLoopStrengthReducePass.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_module_AddConstantMergePass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddDeadArgumentEliminationPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddCallGraphDOTPrinterPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddModuleDebugInfoPrinterPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddAlwaysInlinerPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddReversePostOrderFunctionAttrsPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddGlobalDCEPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddGlobalOptPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddIPSCCPPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddInternalizePass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddLoopExtractorPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddMergeFunctionsPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddPartialInlinerPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddStripSymbolsPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddStripDeadDebugInfoPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddStripDeadPrototypesPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddStripDebugDeclarePass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddStripNonDebugSymbolsPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddArgumentPromotionPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_module_AddPostOrderFunctionAttrsPass.argtypes = [
    ffi.LLVMModulePassManagerRef, ]

ffi.lib.LLVMPY_DisposeNewModulePassManger.argtypes = [
    ffi.LLVMModulePassManagerRef,]

ffi.lib.LLVMPY_AddRefPrunePass_module.argtypes = [
    ffi.LLVMModulePassManagerRef, c_int, c_size_t,
]

# FunctionPassManager

ffi.lib.LLVMPY_CreateNewFunctionPassManager.restype = \
    ffi.LLVMFunctionPassManagerRef

ffi.lib.LLVMPY_RunNewFunctionPassManager.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ffi.LLVMValueRef,
    ffi.LLVMPassBuilderRef,]

ffi.lib.LLVMPY_function_AddAAEvaluator.argtypes = [
    ffi.LLVMFunctionPassManagerRef,]

ffi.lib.LLVMPY_function_AddSimplifyCFGPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef,]

ffi.lib.LLVMPY_function_AddLoopUnrollPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef,]

ffi.lib.LLVMPY_function_AddInstCombinePass.argtypes = [
    ffi.LLVMFunctionPassManagerRef,]

ffi.lib.LLVMPY_AddJumpThreadingPass_function.argtypes = [
    ffi.LLVMFunctionPassManagerRef, c_int,]

ffi.lib.LLVMPY_function_AddCFGPrinterPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddCFGOnlyPrinterPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddDomPrinter.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddDomOnlyPrinter.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddPostDomPrinter.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddPostDomOnlyPrinter.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddDomViewer.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddDomOnlyViewer.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddPostDomViewer.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddPostDomOnlyViewer.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLintPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddADCEPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddBreakCriticalEdgesPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddDSEPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddDCEPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddAggressiveInstCombinePass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLCSSAPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddNewGVNPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLoopSimplifyPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLoopUnrollAndJamPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddSCCPPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLowerAtomicPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLowerInvokePass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLowerSwitchPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddMemCpyOptPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddUnifyFunctionExitNodesPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddReassociatePass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddRegToMemPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddSROAPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddSinkingPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddTailCallElimPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddInstructionNamerPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLoopRotatePass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLoopDeletionPass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_function_AddLoopStrengthReducePass.argtypes = [
    ffi.LLVMFunctionPassManagerRef, ]

ffi.lib.LLVMPY_DisposeNewFunctionPassManger.argtypes = [
    ffi.LLVMFunctionPassManagerRef,]

ffi.lib.LLVMPY_AddRefPrunePass_function.argtypes = [
    ffi.LLVMFunctionPassManagerRef, c_int, c_size_t,
]

# PipelineTuningOptions

ffi.lib.LLVMPY_CreatePipelineTuningOptions.restype = \
    ffi.LLVMPipelineTuningOptionsRef

ffi.lib.LLVMPY_PTOGetLoopInterleaving.restype = c_bool
ffi.lib.LLVMPY_PTOGetLoopInterleaving.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef,]

ffi.lib.LLVMPY_PTOSetLoopInterleaving.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef, c_bool]

ffi.lib.LLVMPY_PTOGetLoopVectorization.restype = c_bool
ffi.lib.LLVMPY_PTOGetLoopVectorization.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef,]

ffi.lib.LLVMPY_PTOSetLoopVectorization.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef, c_bool]

ffi.lib.LLVMPY_PTOGetSLPVectorization.restype = c_bool
ffi.lib.LLVMPY_PTOGetSLPVectorization.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef,]

ffi.lib.LLVMPY_PTOSetSLPVectorization.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef, c_bool]

ffi.lib.LLVMPY_PTOGetLoopUnrolling.restype = c_bool
ffi.lib.LLVMPY_PTOGetLoopUnrolling.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef,]

ffi.lib.LLVMPY_PTOSetLoopUnrolling.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef, c_bool]

ffi.lib.LLVMPY_PTOGetInlinerThreshold.restype = c_int

ffi.lib.LLVMPY_PTOSetInlinerThreshold.argtypes = [
    ffi.LLVMPipelineTuningOptionsRef, c_int]

ffi.lib.LLVMPY_DisposePipelineTuningOptions.argtypes = \
    [ffi.LLVMPipelineTuningOptionsRef,]

# PassBuilder

ffi.lib.LLVMPY_CreatePassBuilder.restype = ffi.LLVMPassBuilderRef
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 = \
    ffi.LLVMModulePassManagerRef

ffi.lib.LLVMPY_buildPerModuleDefaultPipeline.argtypes = [
    ffi.LLVMPassBuilderRef, c_int, c_int]

ffi.lib.LLVMPY_buildFunctionSimplificationPipeline.restype = \
    ffi.LLVMFunctionPassManagerRef

ffi.lib.LLVMPY_buildFunctionSimplificationPipeline.argtypes = [
    ffi.LLVMPassBuilderRef, c_int, c_int]
