"""
Just-in-time compilation support.
"""
import abc
import copy
import shutil
import tempfile
import ctree
from ctree.nodes import Project
from ctree.analyses import VerifyOnlyCtreeNodes
from ctree.util import highlight
import llvm.core as ll
import logging
log = logging.getLogger(__name__)
[docs]class JitModule(object):
    """
    Manages compilation of multiple ASTs.
    """
    def __init__(self):
        import os
        # write files to $TEMPDIR/ctree/run-XXXX
        ctree_dir = os.path.join(tempfile.gettempdir(), "ctree")
        if not os.path.exists(ctree_dir):
            os.mkdir(ctree_dir)
        self.compilation_dir = tempfile.mkdtemp(prefix="run-", dir=ctree_dir)
        self.ll_module = ll.Module.new('ctree')
        self.exec_engine = None
        log.info("temporary compilation directory is: %s",
                 self.compilation_dir)
    def __del__(self):
        if not ctree.CONFIG.get("jit", "PRESERVE_SRC_DIR"):
            log.info("removing temporary compilation directory %s.",
                     self.compilation_dir)
            shutil.rmtree(self.compilation_dir)
    def _link_in(self, submodule):
        self.ll_module.link_in(submodule)
[docs]    def get_callable(self, entry_point_name, entry_point_typesig):
        """
        Returns a python callable that dispatches to the requested C function.
        """
        # get llvm represetation of function
        ll_function = self.ll_module.get_function_named(entry_point_name)
        # run jit compiler
        from llvm.ee import EngineBuilder
        self.exec_engine = \
            
EngineBuilder.new(self.ll_module).mcjit(True).opt(3).create()
        c_func_ptr = self.exec_engine.get_pointer_to_function(ll_function)
        # cast c_func_ptr to python callable using ctypes
        return entry_point_typesig(c_func_ptr)
  
[docs]class ConcreteSpecializedFunction(object):
    """
    A function backed by generated code.
    """
    __metaclass__ = abc.ABCMeta
    def _compile(self, entry_point_name, project_node, entry_point_typesig,
                 **kwargs):
        """
        Returns a python callable.
        """
        assert isinstance(project_node, Project), \
            
"Expected a Project but it got a %s." % type(project_node)
        VerifyOnlyCtreeNodes().visit(project_node)
        self._module = project_node.codegen(**kwargs)
        highlighted = highlight(str(self._module.ll_module), 'llvm')
        log.debug("full LLVM program is: <<<\n%s\n>>>" % highlighted)
        return self._module.get_callable(entry_point_name, entry_point_typesig)
    @abc.abstractmethod
    def __call__(self, *args, **kwargs):
        pass
 
[docs]class LazySpecializedFunction(object):
    """
    A callable object that will produce executable
    code just-in-time.
    """
    def __init__(self, py_ast):
        self.original_tree = py_ast
        self.concrete_functions = {}  # config -> callable map
        self._tuner = self.get_tuning_driver()
    @staticmethod
    def _hash(o):
        if isinstance(o, dict):
            return hash(frozenset(
                LazySpecializedFunction._hash(item) for item in o.items()
            ))
        else:
            return hash(str(o))
    def __call__(self, *args, **kwargs):
        """
        Determines the program_configuration to be run. If it has yet to be
        built, build it. Then, execute it.
        """
        ctree.STATS.log("specialized function call")
        assert not kwargs, \
            
"Passing kwargs to specialized functions isn't supported."
        log.info("detected specialized function call with arg types: %s",
                 [type(a) for a in args])
        args_subconfig = self.args_to_subconfig(args)
        tuner_subconfig = next(self._tuner.configs)
        program_config = (args_subconfig, tuner_subconfig)
        log.info("tuner subconfig: %s", tuner_subconfig)
        log.info("arguments subconfig: %s", args_subconfig)
        config_hash = hash((self._hash(args_subconfig),
                            self._hash(tuner_subconfig)))
        if config_hash in self.concrete_functions:
            ctree.STATS.log("specialized function cache hit")
            log.info("specialized function cache hit!")
        else:
            ctree.STATS.log("specialized function cache miss")
            log.info("specialized function cache miss.")
            transform_result = self.transform(
                copy.deepcopy(self.original_tree),
                program_config
            )
            try:
                try:
                    csf = self.finalize(*transform_result)
                except TypeError:
                    csf = self.finalize(transform_result, program_config)
            except NotImplementedError:
                log.warn("""Your lazy specailized function has not implemented
                         finalize, assuming your output to transform is a
                         concrete specialized function.""")
                csf = transform_result
            assert isinstance(csf, ConcreteSpecializedFunction), \
                
"Expected a ctree.jit.ConcreteSpecializedFunction, \
                 but got a %s." % type(csf)
            self.concrete_functions[config_hash] = csf
        return self.concrete_functions[config_hash](*args, **kwargs)
[docs]    def report(self, *args, **kwargs):
        """
        Records the performance of the most recent configuration.
        """
        return self._tuner.report(*args, **kwargs)
    # =====================================================
    # Methods to be overridden by the user
 
[docs]    def finalize(self, tree, program_config):
        """
        This function will be passed the result of transform.  The specializer
        should return an ConcreteSpecializedFunction.
        """
        raise NotImplementedError()
 
[docs]    def get_tuning_driver(self):
        """
        Define the space of possible implementations.
        """
        from ctree.tune import ConstantTuningDriver
        return ConstantTuningDriver()
 
[docs]    def args_to_subconfig(self, args):
        """
        Extract features from the arguments to define uniqueness of
        this particular invocation. The return value must be a hashable
        object, or a dictionary of hashable objects.
        """
        log.warn("arguments will not influence program_config. " +
                 "Consider overriding args_to_subconfig() in %s.",
                 type(self).__name__)
        return dict()