"""Lowers CFG-IR to tree-IR."""

import modelverse_jit.cfg_ir as cfg_ir
import modelverse_jit.tree_ir as tree_ir
import modelverse_jit.runtime as jit_runtime
import modelverse_jit.bytecode_to_tree as bytecode_to_tree

# The CFG deconstruction code here is based on the relooper algorithm
# as detailed in https://github.com/kripken/Relooper/blob/master/paper.pdf

class FlowGraphComponent(object):
    """Represents a control-flow graph component."""
    def __init__(self, entry_blocks, blocks, reachable):
        self.entry_blocks = entry_blocks
        self.blocks = blocks
        self.reachable = reachable

    def get_entry_reachable_blocks(self):
        """Gets the set of all blocks that are reachable from the entry points."""
        return set.union(*[self.get_reachable_blocks(block) for block in self.entry_blocks])

    def get_reachable_blocks(self, block):
        """Gets all blocks in this component that are reachable from the given block."""
        return set.intersection(self.reachable[block], self.blocks)

    def get_directly_reachable_blocks(self, block):
        """Gets all blocks in this component that are reachable from the given block by taking
           exactly one branch."""
        return set.intersection(cfg_ir.get_directly_reachable_blocks(block), self.blocks)

    def can_reach(self, source_block, target_block):
        """Tests if the first block can reach the second."""
        return target_block in self.reachable[source_block] and target_block in self.blocks

    def sub_component(self, new_entry_points):
        """Creates a sub-component of this component, with the given new entry points."""
        result = FlowGraphComponent(new_entry_points, self.blocks, self.reachable)
        result.blocks = result.get_entry_reachable_blocks()
        return result

def create_graph_component(entry_point):
    """Creates a flow graph component from the given entry point."""
    reachable = cfg_ir.get_all_reachable_blocks(entry_point)
    all_blocks = set(reachable[entry_point])
    all_blocks.add(entry_point)
    return FlowGraphComponent([entry_point], all_blocks, reachable)

class SimpleBlock(object):
    """A 'simple' block in the relooper algorithm."""
    def __init__(self, body, next_block):
        self.body = body
        self.next_block = next_block

    def lower(self, state):
        """Lowers this 'simple' block to a tree."""
        return tree_ir.create_block(
            state.lower_block(self.body),
            self.next_block.lower(state))

class EmptyBlock(object):
    """An empty relooper block."""
    def lower(self, _):
        """Lowers this empty block to a tree."""
        return tree_ir.EmptyInstruction()

class LoopBlock(object):
    """A 'loop' block in the relooper algorithm."""
    def __init__(self, inner, next_block):
        self.inner = inner
        self.next_block = next_block

    def lower(self, state):
        """Lowers this 'loop' block to a tree."""
        return tree_ir.create_block(
            tree_ir.LoopInstruction(self.inner.lower(state)),
            self.next_block.lower(state))

class MultipleBlock(object):
    """A 'multiple' block in the relooper algorithm that does _not_ require a loop."""
    def __init__(self, handled_blocks, next_block):
        self.handled_blocks = handled_blocks
        self.next_block = next_block

    def lower_handled_blocks(self, state):
        """Lowers the handled blocks of this 'multiple' block to a tree."""
        cond_clause_pairs = []
        for entry, block in self.handled_blocks:
            cond_clause_pairs.append((
                tree_ir.BinaryInstruction(
                    tree_ir.LoadLocalInstruction(state.label_variable),
                    '==',
                    tree_ir.LiteralInstruction(entry.index)),
                block.lower(state)))
        return tree_ir.SwitchInstruction(cond_clause_pairs)

    def lower(self, state):
        """Lowers this 'multiple' block to a tree."""
        return tree_ir.create_block(
            self.lower_handled_blocks(state),
            self.next_block.lower(state))

class MultipleLoopBlock(MultipleBlock):
    """A 'multiple' block in the relooper algorithm."""
    def __init__(self, handled_blocks, next_block):
        MultipleBlock.__init__(self, handled_blocks, next_block)

    def lower(self, state):
        """Lowers this 'multiple' block to a tree."""
        return tree_ir.create_block(
            tree_ir.LoopInstruction(self.lower_handled_blocks(state)),
            self.next_block.lower(state))

class ContinueFlow(cfg_ir.FlowInstruction):
    """Represents a control flow instruction which continues to the next loop iteration."""
    def __init__(self, loop):
        cfg_ir.FlowInstruction.__init__(self)
        self.loop = loop

    def get_dependencies(self):
        """Gets all definitions and instructions on which this instruction depends."""
        return []

    def branches(self):
        """Gets a list of basic blocks targeted by this flow instruction."""
        return []

    def __str__(self):
        return 'continue'

class BreakFlow(cfg_ir.FlowInstruction):
    """Represents a control flow instruction which breaks out of a loop."""
    def __init__(self, loop):
        cfg_ir.FlowInstruction.__init__(self)
        self.loop = loop

    def get_dependencies(self):
        """Gets all definitions and instructions on which this instruction depends."""
        return []

    def branches(self):
        """Gets a list of basic blocks targeted by this flow instruction."""
        return []

    def __str__(self):
        return 'break'

def solipsize(graph_component, loop, entry_blocks, next_blocks):
    """Replaces branches to entry blocks and next blocks by jumps to 'continue' and 'break'
       blocks, respectively."""
    all_blocks = set()
    reachable_from_inner = set()
    for block in graph_component.blocks:
        if block in next_blocks:
            continue

        all_blocks.add(block)
        for branch in block.flow.branches():
            if branch.block in entry_blocks:
                # Create a 'continue' block, and point to that.
                continue_block = cfg_ir.BasicBlock(block.counter)
                for param in branch.block.parameters:
                    continue_block.append_parameter(param)

                continue_block.flow = ContinueFlow(loop)
                branch.block = continue_block
                all_blocks.add(continue_block)
            elif branch.block in next_blocks:
                # Create a 'break' block, and point to that.
                break_block = cfg_ir.BasicBlock(block.counter)
                for param in branch.block.parameters:
                    break_block.append_parameter(param)

                break_block.flow = BreakFlow(loop)
                branch.block = break_block
                all_blocks.add(break_block)
                reachable_from_inner.add(branch.block)

    return reachable_from_inner, FlowGraphComponent(
        graph_component.entry_blocks,
        all_blocks,
        {
            block : cfg_ir.get_reachable_blocks(block)
            for block in all_blocks
        })

def to_relooper_loop(graph_component):
    """Converts the given graph component to a relooper 'loop'."""
    entry_blocks = graph_component.entry_blocks
    result = LoopBlock(None, None)
    inner_blocks = []
    next_blocks = []
    for block in graph_component.blocks:
        if any([graph_component.can_reach(block, ep) for ep in entry_blocks]):
            inner_blocks.append(block)
        else:
            next_blocks.append(block)

    reachable_from_inner, inner_component = solipsize(
        graph_component, result, entry_blocks, next_blocks)
    result.inner = reloop(inner_component)

    next_component = FlowGraphComponent(
        reachable_from_inner, next_blocks, graph_component.reachable)
    result.next_block = reloop(next_component)

    return result

def to_relooper_multiple_or_loop(graph_component):
    """Converts the given graph component to a relooper 'multiple' or 'loop'."""
    # From the Emscripten paper:
    #
    # If we have more than one entry, try to create a Multiple block:
    # For each entry, find all the labels it reaches that
    # cannot be reached by any other entry. If at least one entry
    # has such labels, return a Multiple block, whose Handled
    # blocks are blocks for those labels (and whose entries are
    # those labels), and whose Next block is all the rest. Entries
    # for the next block are entries that did not become part of
    # the Handled blocks, and also labels that can be reached
    # from the Handled blocks.
    entry_blocks = graph_component.entry_blocks
    if len(entry_blocks) <= 1:
        return to_relooper_loop(graph_component)

    entry_reachables = {ep : graph_component.get_reachable_blocks(ep) for ep in entry_blocks}
    exclusive_entries = {}
    for entry in entry_blocks:
        exclusive_blocks = set(entry_reachables[entry])
        for other_entry in entry_blocks:
            if other_entry != entry:
                exclusive_blocks.difference_update(entry_reachables[other_entry])
        if len(exclusive_blocks) > 0:
            exclusive_entries[entry] = exclusive_blocks

    if len(exclusive_entries) == 0:
        return to_relooper_loop(graph_component)

    next_entries = set(graph_component.entry_blocks)
    for block_set in exclusive_entries.values():
        for elem in block_set:
            directly_reachable = graph_component.get_directly_reachable_blocks(elem)
            directly_reachable.remove(elem)
            next_entries.update(directly_reachable)

    next_entries.difference_update(exclusive_entries.keys())

    result = MultipleLoopBlock({}, None)
    for entry, exclusive_blocks in exclusive_entries.items():
        other_blocks = set(graph_component.blocks)
        other_blocks.difference_update(exclusive_blocks)
        result.handled_blocks[entry] = reloop(
            solipsize(graph_component, result, set([entry]), other_blocks))

    result.next_block = reloop(graph_component.sub_component(next_entries))

    return result

def reloop(graph_component):
    """Applies the relooper algorithm to the given graph component."""
    entry_blocks = graph_component.entry_blocks
    if len(entry_blocks) == 0:
        return EmptyBlock()

    reachable_set = graph_component.get_entry_reachable_blocks()
    if len(entry_blocks) == 1 and entry_blocks[0] not in reachable_set:
        graph_component.blocks.remove(entry_blocks[0])
        return SimpleBlock(
            entry_blocks[0],
            reloop(
                FlowGraphComponent(
                    graph_component.get_directly_reachable_blocks(entry_blocks[0]),
                    graph_component.blocks,
                    graph_component.reachable)))
    elif all([block in reachable_set for block in entry_blocks]):
        return to_relooper_loop(graph_component)
    else:
        return to_relooper_multiple_or_loop(graph_component)

def reloop_trivial(graph_component):
    """Converts the given control-flow graph to a 'multiple' block that contains only 'simple'
       blocks."""
    return MultipleLoopBlock(
        [(block, SimpleBlock(block, EmptyBlock())) for block in graph_component.blocks],
        EmptyBlock())

def reloop_function_body(entry_point):
    """Reloops the control-flow graph defined by the given entry point."""
    return reloop_trivial(create_graph_component(entry_point))

def find_inlinable_definitions(entry_point):
    """Computes a set of definitions which are eligible for inlining, i.e., they
       are used only once and are consumed in the same basic block in which they
       are defined."""
    def_users, def_blocks = cfg_ir.find_all_def_uses(entry_point)

    # Find all definitions which are eligible for inlining.
    eligible_defs = set()
    for definition, users in def_users.items():
        if len(users) == 1:
            single_user = next(iter(users))
            if def_blocks[single_user] == def_blocks[definition]:
                eligible_defs.add(definition)

    return eligible_defs

def lower_gc_protect(protected_value, root):
    """Lowers a GC_PROTECT_MACRO_NAME macro call."""
    return tree_ir.IgnoreInstruction(tree_ir.CreateEdgeInstruction(root, protected_value))

def lower_maybe_gc_protect(condition, protected_value, root):
    """Lowers a MAYBE_GC_PROTECT_MACRO_NAME macro call."""
    return tree_ir.SelectInstruction(
        tree_ir.BinaryInstruction(condition, 'is not', tree_ir.LiteralInstruction(None)),
        lower_gc_protect(protected_value, root),
        tree_ir.EmptyInstruction())

def lower_register_debug_info(function_name, source_map, origin):
    """Lowers a REGISTER_DEBUG_INFO_MACRO_NAME call."""
    assert isinstance(source_map, tree_ir.LiteralInstruction)
    return tree_ir.RegisterDebugInfoInstruction(
        function_name, tree_ir.LoadGlobalInstruction(source_map.literal), origin)

class SimpleDefinitionScheduler(object):
    """Schedules definitions within a basic block in the order they occur in."""
    def __init__(self, lowering_state, definitions, flow):
        self.lowering_state = lowering_state
        self.definition_queue = definitions + [flow]
        self.definition_set = set(self.definition_queue)

    def use(self, definition):
        """Creates a tree that produces the given definition's value."""
        def_value = cfg_ir.get_def_value(definition)
        if isinstance(def_value, LoweringState.inline_value_types):
            return self.lowering_state.lower_value(def_value)
        elif isinstance(def_value, cfg_ir.AllocateRootNode):
            return tree_ir.LoadLocalInstruction(
                self.lowering_state.get_root_node_name(def_value))
        elif definition.has_value():
            return self.lowering_state.create_definition_load(definition)
        else:
            return tree_ir.EmptyInstruction()

    def get_schedulable_definition(self):
        """Finds a schedulable definition. Returns None if there are no definitions left to
           schedule."""
        if len(self.definition_queue) == 0:
            return None
        else:
            return self.definition_queue[-1]

    def __schedule_single_def(self, definition, store_and_forget, statements):
        if isinstance(definition, cfg_ir.FlowInstruction):
            statements.append(self.lowering_state.lower_value(definition))
        else:
            # We're sure that we're dealing with a bona fide cfg_ir.Definition now.
            definition_value = definition.value
            if cfg_ir.is_value_def(definition_value, LoweringState.inline_value_types):
                pass
            else:
                lowered_value = self.lowering_state.lower_value(definition_value)
                if (definition.debug_information is not None
                        and self.lowering_state.jit.source_maps_enabled):
                    lowered_value = tree_ir.DebugInfoInstruction(
                        lowered_value, definition.debug_information)

                if definition.has_value():
                    def_load = self.lowering_state.create_definition_load(definition)
                    statements.append(
                        tree_ir.IgnoreInstruction(def_load.create_store(lowered_value)))
                    if not store_and_forget:
                        statements.append(def_load)
                else:
                    statements.append(lowered_value)

    def has_scheduled(self, definition):
        """Tests if the given definition has already been scheduled."""
        return definition not in self.definition_set

    def schedule(self, definition, store_and_forget=False):
        """Schedules the given definition. The resulting tree is returned."""
        assert definition in self.definition_set

        index = self.definition_queue.index(definition)
        defs_to_schedule = self.definition_queue[:index + 1]
        del self.definition_queue[:index + 1]

        statements = []
        for schedule_def in defs_to_schedule:
            self.definition_set.remove(schedule_def)
            self.__schedule_single_def(schedule_def, store_and_forget, statements)

        return tree_ir.create_block(*statements)

class DependencyDefinitionScheduler(object):
    """Schedules definitions within a basic block in an order that tries to maximize
       the inlining of expressions."""
    # We now have the opportunity to schedule definitions in a somewhat intelligent way.
    # We ought to make sure that we don't reorder operations in an observable way, though.
    # Specifically:
    #
    #     We are allowed to reorder operations which have no side-effects,
    #     as long as they do not depend on each other. So this is fine:
    #
    #         $a = load $x
    #         $b = load $y
    #         $c = call-indirect 'jit' f($b, $a)
    #
    #     ==>
    #
    #         $c = call-indirect 'jit' f(load $y, load $x)
    #
    #     But we are not allowed to reorder operations which do have side-effects.
    #     This is _not_ okay:
    #
    #         $a = load $x
    #         $b = call-indirect 'jit' g()
    #         $c = store $x, $a
    #
    #     ==>
    #
    #         $b = call-indirect 'jit' g()
    #         $c = store $x, load $x
    #
    # We can combine the concepts of depending on the results of other instructions
    # and depending on the potential side-effects of instructions by extending the notion
    # of what a dependency is: every instruction depends on its explicit dependencies,
    # and has an implicit dependency on all effectful instructions that precede it.
    # Additionally, every instruction with side-effects also depends on every instruction
    # that precedes it, regardless of whether those instructions have side-effects or not.
    #
    # The advantage of this way of looking at things is that we can now construct a dependency
    # graph. For example, this is the dependency graph for the second example.
    #
    #                                $c = store $x, $a
    #                               /                 \
    #                              /                   \
    #                             v                     v
    #                    $a = load $x <------------- $b = call-indirect 'jit' g()
    #
    # Any topological sort of the dependency graph is a valid instruction scheduling, but we
    # specifically want to grow a tree that uses few temporaries.
    #
    # We can accomplish this by growing the tree in reverse: we pick a definition on which no other
    # definition depends, and remove it from the dependency graph. We then iterate over the
    # definition's dependencies in reverse. If a dependency is encountered that is eligible for
    # inlining, i.e., it is used only once and is defined in the basic block where it is used,
    # then we really want to schedule it inline -- doing so allows us to avoid defining a temporary.
    # Here's the algorithm that we'll use to schedule dependencies.
    #
    # while (dependency has not been scheduled) and (another definition depends on the dependency):
    #     pick a definition that depends on the dependency and schedule it
    #     store the scheduled definition's value in a temporary
    #
    # if (dependency has not been scheduled) and (dependency is eligible for inlining):
    #     schedule the dependency inline
    # else if (dependency has not been scheduled):
    #     schedule the dependency, and store it in a temporary
    # else:
    #     load the (already scheduled) dependency's value from a temporary
    #
    # Note that it is possible for another definition to also depend on a dependency, despite having
    # the dependency used only once. This merely means that at least one dependency is based on an
    # instruction's side-effects.
    #
    # Okay, so that's the theory. In this class, we will use the following data structure to
    # improve performance:
    #
    #     * We will implement our graph as a map of definitions to a
    #       (outgoing edges, incoming edges) tuple. This will allow us to quickly test if a
    #       definition is a dependency of some other definition, and will also allow us to
    #       erase definitions from the graph efficiently.
    #
    #     * We will not create implicit (side-effect--based) dependencies beyond the last
    #       definition with side-effects. Said definition already depends on every definition
    #       that precedes it, so those edges are simply redundant.
    #
    def __init__(self, lowering_state, definitions, flow):
        self.lowering_state = lowering_state
        self.dependency_graph = {}
        self.construct_dependency_graph(definitions + [flow])

    def add_dependency(self, definition, dependency):
        """Makes the given definition dependent on the given dependency in the
           dependency graph."""
        def_outgoing_edges, _ = self.dependency_graph[definition]
        _, dep_incoming_edges = self.dependency_graph[dependency]
        def_outgoing_edges.add(dependency)
        dep_incoming_edges.add(definition)

    def construct_dependency_graph(self, definitions):
        """Construct the dependency graph for the given set of definitions."""
        def_set = set(definitions)

        # Initialize the dependency graph for every definition in the basic block.
        for definition in definitions:
            self.dependency_graph[definition] = (set(), set())

        # Construct the dependency graph.
        last_def_batch = []
        last_effectful_def = None
        for definition in definitions:
            # Explicit dependencies.
            for dep in definition.get_all_dependencies():
                if dep in def_set:
                    self.add_dependency(definition, dep)

            # Implicit dependency on the previous definition with side-effects.
            if last_effectful_def is not None:
                self.add_dependency(definition, last_effectful_def)

            # Implicit dependency of definitions with side-effects on all definitions
            # that precede them. Implementation detail: we don't actually make
            # definitions dependent on _all_ definitions that precede them. It suffices
            # to insert a dependency on every definition between the current definition
            # and the previous definition with side-effects.
            if definition.has_side_effects():
                for implicit_dependency in last_def_batch:
                    self.add_dependency(definition, implicit_dependency)
                last_def_batch = []
                last_effectful_def = definition
            else:
                last_def_batch.append(definition)

    def remove_definition(self, definition):
        """Removes the given definition from the graph."""
        assert not self.has_dependents(definition)

        # Remove the definition from the graph.
        outgoing_edges, _ = self.dependency_graph[definition]
        del self.dependency_graph[definition]

        # Remove the definition's outgoing edges from the graph.
        for dependency in outgoing_edges:
            _, incoming_edges = self.dependency_graph[dependency]
            incoming_edges.remove(definition)

    def has_scheduled(self, definition):
        """Tests if the given definition has already been scheduled."""
        return definition not in self.dependency_graph

    def get_schedulable_definition(self):
        """Finds a schedulable definition. Returns None if the dependency graph is empty."""
        if len(self.dependency_graph) == 0:
            return None

        for node in self.dependency_graph.keys():
            if not self.has_dependents(node):
                return node

        raise ValueError(
            'Could not find an instruction to schedule. Dependency graph: %s' %
            self.dependency_graph)

    def has_dependents(self, definition):
        """Tests if there is at least one definition in the dependency graph that
           depends on the given definition."""
        return len(self.get_dependents(definition)) > 0

    def get_dependents(self, definition):
        """Gets the set of definitions that depend on the given definition."""
        _, incoming_edges = self.dependency_graph[definition]
        return incoming_edges

    def use(self, definition):
        """Creates a tree that produces the given definition's value."""
        def_value = cfg_ir.get_def_value(definition)
        if isinstance(def_value, LoweringState.inline_value_types):
            return self.lowering_state.lower_value(def_value)
        elif isinstance(def_value, cfg_ir.AllocateRootNode):
            return tree_ir.LoadLocalInstruction(
                self.lowering_state.get_root_node_name(def_value))
        elif not self.has_scheduled(definition):
            return self.schedule(definition)
        elif definition.has_value():
            return self.lowering_state.create_definition_load(definition)
        else:
            return tree_ir.EmptyInstruction()

    def __schedule_definition(self, definition, store_and_forget):
        """Schedules the given definition, excluding definitions that are dependent on it.
           A list of trees is returned."""
        assert not self.has_scheduled(definition)
        statements = []

        # print('Scheduling %s; store_and_forget: %s' % (definition, store_and_forget))

        self.remove_definition(definition)
        if isinstance(definition, cfg_ir.FlowInstruction):
            statements.append(self.lowering_state.lower_value(definition))
        else:
            # We're sure that we're dealing with a bona fide cfg_ir.Definition now.
            definition_value = definition.value
            if cfg_ir.is_value_def(definition_value, LoweringState.inline_value_types):
                pass
            elif (not store_and_forget
                  and definition in self.lowering_state.inlinable_definitions):
                statements.append(self.lowering_state.lower_value(definition_value))
            else:
                lowered_value = self.lowering_state.lower_value(definition_value)
                if definition.has_value():
                    def_load = self.lowering_state.create_definition_load(definition)
                    statements.append(
                        tree_ir.IgnoreInstruction(def_load.create_store(lowered_value)))
                    if not store_and_forget:
                        statements.append(def_load)
                else:
                    statements.append(lowered_value)

        # print('Done scheduling %s; result: %s' % (definition, tree_ir.create_block(*statements)))

        return statements

    def __schedule_top_of_stack(self, stack, statements, store_and_forget):
        """Tries to schedule the given stack's top-of-stack element."""
        definition = stack[-1]
        if self.has_scheduled(definition):
            # Definition has already been scheduled. Load it if it is the only
            # element on the stack. Otherwise, do nothing.
            stack.pop()
            if (not store_and_forget
                    and definition.has_value()
                    and not cfg_ir.is_value_def(definition, LoweringState.inline_value_types)):
                statements.append(self.lowering_state.create_definition_load(definition))
            return statements

        dependents = self.get_dependents(definition)
        if len(dependents) > 0:
            dependent = next(iter(dependents))
            stack.append(dependent)
            return statements
        else:
            # Definition has not been scheduled, and all of its dependent definitions
            # have been scheduled.
            stack.pop()
            return self.__schedule_definition(definition, store_and_forget) + statements

    def schedule(self, definition, store_and_forget=False):
        """Schedules the given definition. The resulting tree is returned."""
        assert not self.has_scheduled(definition)

        statements = []
        schedule_stack = [definition]

        while len(schedule_stack) > 0:
            statements = self.__schedule_top_of_stack(
                schedule_stack, statements, store_and_forget or len(schedule_stack) > 1)

        return tree_ir.create_block(*statements)

class LoweringState(object):
    """Stores information related to the relooper->tree conversion."""
    def __init__(self, jit, inlinable_definitions):
        self.jit = jit
        self.label_variable = tree_ir.VariableName('__label')
        self.definition_loads = {}
        self.local_name_map = bytecode_to_tree.LocalNameMap()
        self.root_edge_names = {}
        self.inlinable_definitions = inlinable_definitions
        self.scheduler = None

    def __get_root_edge_name(self, root_node):
        """Gets the name of the given root edge's variable."""
        return self.get_root_node_name(root_node) + '_edge'

    def get_root_node_name(self, root_node):
        """Gets the name of the given root node's variable."""
        if isinstance(root_node, cfg_ir.Definition):
            return self.get_root_node_name(root_node.value)

        if root_node in self.root_edge_names:
            return self.root_edge_names[root_node]

        result = 'jit_locals%d' % len(self.root_edge_names)
        self.root_edge_names[root_node] = result
        return result

    def create_definition_load(self, definition):
        """Loads the given definition's variable."""
        if definition in self.definition_loads:
            return self.definition_loads[definition]

        result = tree_ir.LoadLocalInstruction(None)
        self.definition_loads[definition] = result
        return result

    def use_definition(self, definition):
        """Loads the given definition's value."""
        return self.scheduler.use(definition)

    def lower_block(self, block):
        """Lowers the given (relooped) block to a tree."""
        statements = []
        self.scheduler = SimpleDefinitionScheduler(self, block.definitions, block.flow)
        while 1:
            schedulable_def = self.scheduler.get_schedulable_definition()
            if schedulable_def is None:
                break

            statements.append(self.scheduler.schedule(schedulable_def, True))

        self.scheduler = None

        statements.reverse()
        return tree_ir.create_block(*statements)

    def lower_value(self, value):
        """Lowers the given instruction to a tree."""
        value_type = type(value)
        if value_type in LoweringState.instruction_lowerings:
            return LoweringState.instruction_lowerings[value_type](self, value)
        else:
            raise jit_runtime.JitCompilationFailedException(
                "Unknown CFG instruction: '%s'" % value)

    def lower_literal(self, value):
        """Lowers the given literal value."""
        return tree_ir.LiteralInstruction(value.literal)

    def lower_check_local_exists(self, value):
        """Lowers a 'check-value-exists' value."""
        return tree_ir.LocalExistsInstruction(
            self.local_name_map.get_local_name(value.variable.node_id))

    def lower_declare_local(self, value):
        """Lowers a 'declare-local' value."""
        local_name = self.local_name_map.get_local_name(value.variable.node_id)
        return tree_ir.create_block(
            tree_ir.create_new_local_node(
                local_name,
                self.use_definition(value.root_node)),
            tree_ir.LoadLocalInstruction(local_name))

    def lower_resolve_local(self, value):
        """Lowers a 'resolve-local' value."""
        return tree_ir.LoadLocalInstruction(
            self.local_name_map.get_local_name(value.variable.node_id))

    def lower_declare_global(self, value):
        """Lowers a 'declare-global' value."""
        #
        #     global_var, = yield [("CN", [])]
        #     _globals, = yield [("RD", [task_root, "globals"])]
        #     yield [("CD", [_globals, var_name, global_var])]
        #
        global_var = tree_ir.StoreLocalInstruction(None, tree_ir.CreateNodeInstruction())
        return tree_ir.create_block(
            global_var.create_store(
                tree_ir.CreateNodeInstruction()),
            tree_ir.CreateDictionaryEdgeInstruction(
                tree_ir.ReadDictionaryValueInstruction(
                    bytecode_to_tree.load_task_root(),
                    tree_ir.LiteralInstruction('globals')),
                tree_ir.LiteralInstruction(
                    value.variable.name),
                global_var.create_load()),
            global_var.create_load())

    def lower_resolve_global(self, value):
        """Lowers a 'resolve-global' value."""
        #
        #     _globals, = yield [("RD", [task_root, "globals"])]
        #     global_var, = yield [("RD", [_globals, var_name])]
        #
        return tree_ir.ReadDictionaryValueInstruction(
            tree_ir.ReadDictionaryValueInstruction(
                bytecode_to_tree.load_task_root(),
                tree_ir.LiteralInstruction('globals')),
            tree_ir.LiteralInstruction(value.variable.name))

    def lower_function_parameter(self, value):
        """Lowers a 'function-parameter' value."""
        return tree_ir.LoadLocalInstruction(value.name)

    def lower_alloc_root_node(self, value):
        """Lowers an 'alloc-root-node' value."""
        local_name = tree_ir.VariableName(self.get_root_node_name(value))
        return tree_ir.create_block(
            tree_ir.create_new_local_node(
                local_name,
                tree_ir.LoadIndexInstruction(
                    tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
                    tree_ir.LiteralInstruction('task_root')),
                self.__get_root_edge_name(value)),
            tree_ir.LoadLocalInstruction(local_name))

    def lower_free_root_node(self, value):
        """Lowers a 'free-root-node' value."""
        return tree_ir.DeleteEdgeInstruction(
            tree_ir.LoadLocalInstruction(self.__get_root_edge_name(value.root_node)))

    def lower_load_pointer(self, value):
        """Lowers a 'load' value."""
        return bytecode_to_tree.create_access(self.use_definition(value.pointer))

    def lower_store_pointer(self, value):
        """Lowers a 'store' value."""
        ptr, val = self.use_definition(value.pointer), self.use_definition(value.value)
        return bytecode_to_tree.create_assign(ptr, val)

    def lower_read(self, value):
        """Lowers a 'read' value."""
        return tree_ir.ReadValueInstruction(
            self.use_definition(value.node))

    def lower_create_node(self, value):
        """Lowers a 'create-node' value."""
        if value.value.has_value():
            return tree_ir.CreateNodeWithValueInstruction(
                self.use_definition(value.value))
        else:
            return tree_ir.CreateNodeInstruction()

    def lower_create_edge(self, value):
        """Lowers a 'create-edge' value."""
        source, target = self.use_definition(value.source), self.use_definition(value.target)
        return tree_ir.CreateEdgeInstruction(source, target)

    def lower_binary(self, value):
        """Lowers a 'binary' value."""
        lhs, rhs = self.use_definition(value.lhs), self.use_definition(value.rhs)
        return tree_ir.BinaryInstruction(lhs, value.operator, rhs)

    def lower_unary(self, value):
        """Lowers a 'unary' value."""
        operand = self.use_definition(value.operand)
        return tree_ir.UnaryInstruction(value.operator, operand)

    def lower_direct_call(self, value):
        """Lowers a direct function call."""
        calling_convention = value.calling_convention
        if calling_convention in LoweringState.call_lowerings:
            return LoweringState.call_lowerings[calling_convention](self, value)
        else:
            raise jit_runtime.JitCompilationFailedException(
                "Unknown calling convention: '%s' in instruction '%s'" %
                (calling_convention, value))

    def lower_indirect_call(self, value):
        """Lowers an indirect function call."""
        target = self.use_definition(value.target)
        args = [(name, self.use_definition(arg))
                for name, arg in value.argument_list]
        return bytecode_to_tree.create_indirect_call(target, args)

    def lower_simple_positional_call(self, value):
        """Lowers a direct call that uses the 'simple-positional' calling convention."""
        return tree_ir.CallInstruction(
            tree_ir.LoadGlobalInstruction(value.target_name),
            [self.use_definition(arg) for _, arg in value.argument_list])

    def lower_self_positional_call(self, value):
        """Lowers a direct call that uses the 'self-positional' calling convention."""
        all_args = [self.use_definition(arg) for _, arg in value.argument_list]
        target = all_args[0]
        arg_list = all_args[1:]
        return tree_ir.CallInstruction(
            tree_ir.LoadMemberInstruction(target, value.target_name), arg_list)

    def lower_jit_call(self, value):
        """Lowers a direct call that uses the 'jit' calling convention."""
        arg_list = [(name, self.use_definition(arg))
                    for name, arg in value.argument_list]
        intrinsic = self.jit.get_intrinsic(value.target_name)
        if intrinsic is not None:
            return bytecode_to_tree.apply_intrinsic(intrinsic, arg_list)
        else:
            return tree_ir.create_jit_call(
                tree_ir.LoadGlobalInstruction(value.target_name),
                arg_list,
                tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME))

    def lower_macro_call(self, value):
        """Expands a macro call."""
        arg_list = [self.use_definition(arg) for _, arg in value.argument_list]
        if value.target_name in LoweringState.macro_lowerings:
            return LoweringState.macro_lowerings[value.target_name](*arg_list)
        else:
            raise jit_runtime.JitCompilationFailedException(
                "Unknown macro: '%s' in instruction '%s'" %
                (value.target_name, value))

    def lower_io_call(self, value):
        """Expands an IO call."""
        arg_list = [self.use_definition(arg) for _, arg in value.argument_list]
        if value.target_name == cfg_ir.NOP_MACRO_NAME:
            return tree_ir.NopInstruction()
        elif value.target_name == cfg_ir.INPUT_MACRO_NAME:
            return bytecode_to_tree.create_input(self.jit.use_input_function)
        elif value.target_name == cfg_ir.OUTPUT_MACRO_NAME:
            return bytecode_to_tree.create_output(*arg_list)
        else:
            raise jit_runtime.JitCompilationFailedException(
                "Unknown IO macro: '%s' in instruction '%s'" %
                (value.target_name, value))

    def lower_jump(self, flow):
        """Lowers the given 'jump' flow instruction to a tree."""
        return self.lower_branch(flow.branch)

    def lower_select(self, flow):
        """Lowers the given 'select' flow instruction to a tree."""
        # Schedule all branch arguments, so their definitions don't end up in the
        # 'if' statement.
        statements = []
        for branch in (flow.if_branch, flow.else_branch):
            for arg in branch.arguments:
                if not self.scheduler.has_scheduled(arg):
                    statements.append(self.scheduler.schedule(arg, True))

        statements.append(
            tree_ir.SelectInstruction(
                self.use_definition(flow.condition),
                self.lower_branch(flow.if_branch),
                self.lower_branch(flow.else_branch)))

        return tree_ir.create_block(*statements)

    def lower_return(self, flow):
        """Lowers the given 'return' flow instruction to a tree."""
        return tree_ir.ReturnInstruction(self.use_definition(flow.value))

    def lower_throw(self, flow):
        """Lowers the given 'throw' flow instruction to a tree."""
        return tree_ir.RaiseInstruction(self.use_definition(flow.exception))

    def lower_unreachable(self, _):
        """Lowers the given 'unreachable' flow instruction to a tree."""
        return tree_ir.IgnoreInstruction(
            tree_ir.CallInstruction(
                tree_ir.LoadGlobalInstruction(jit_runtime.UNREACHABLE_FUNCTION_NAME), []))

    def lower_break(self, _):
        """Lowers the given 'break' flow instruction to a tree."""
        return tree_ir.BreakInstruction()

    def lower_continue(self, _):
        """Lowers the given 'continue' flow instruction to a tree."""
        return tree_ir.ContinueInstruction()

    def lower_branch(self, branch):
        """Lowers the given (relooped) branch to a tree."""
        instructions = []
        if len(branch.arguments) > 0:
            instructions.append(
                tree_ir.TupleStoreLocalInstruction(
                    [(self.create_definition_load(param).name, self.use_definition(arg))
                     for param, arg in zip(branch.block.parameters, branch.arguments)]))

        instructions.append(
            tree_ir.IgnoreInstruction(
                tree_ir.StoreLocalInstruction(
                    self.label_variable,
                    tree_ir.LiteralInstruction(branch.block.index))))

        return tree_ir.create_block(*instructions)

    inline_value_types = (cfg_ir.Literal, cfg_ir.ResolveLocal, cfg_ir.FunctionParameter)

    instruction_lowerings = {
        cfg_ir.Literal : lower_literal,
        cfg_ir.CheckLocalExists : lower_check_local_exists,
        cfg_ir.DeclareLocal : lower_declare_local,
        cfg_ir.ResolveLocal : lower_resolve_local,
        cfg_ir.DeclareGlobal : lower_declare_global,
        cfg_ir.ResolveGlobal : lower_resolve_global,
        cfg_ir.FunctionParameter : lower_function_parameter,
        cfg_ir.AllocateRootNode : lower_alloc_root_node,
        cfg_ir.DeallocateRootNode : lower_free_root_node,
        cfg_ir.LoadPointer : lower_load_pointer,
        cfg_ir.StoreAtPointer : lower_store_pointer,
        cfg_ir.Read : lower_read,
        cfg_ir.CreateNode : lower_create_node,
        cfg_ir.CreateEdge : lower_create_edge,
        cfg_ir.Binary : lower_binary,
        cfg_ir.Unary : lower_unary,
        cfg_ir.DirectFunctionCall : lower_direct_call,
        cfg_ir.IndirectFunctionCall : lower_indirect_call,
        cfg_ir.JumpFlow : lower_jump,
        cfg_ir.SelectFlow : lower_select,
        cfg_ir.ReturnFlow : lower_return,
        cfg_ir.ThrowFlow : lower_throw,
        cfg_ir.UnreachableFlow : lower_unreachable,
        BreakFlow : lower_break,
        ContinueFlow : lower_continue
    }

    call_lowerings = {
        cfg_ir.SIMPLE_POSITIONAL_CALLING_CONVENTION : lower_simple_positional_call,
        cfg_ir.SELF_POSITIONAL_CALLING_CONVENTION : lower_self_positional_call,
        cfg_ir.JIT_CALLING_CONVENTION : lower_jit_call,
        cfg_ir.JIT_NO_GC_CALLING_CONVENTION : lower_jit_call,
        cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION : lower_macro_call,
        cfg_ir.MACRO_IO_CALLING_CONVENTION : lower_io_call
    }

    macro_lowerings = {
        cfg_ir.PRINT_MACRO_NAME: tree_ir.PrintInstruction,
        cfg_ir.READ_OUTGOING_EDGES_MACRO_NAME: tree_ir.ReadOutgoingEdgesInstruction,
        cfg_ir.READ_DICT_KEYS_MACRO_NAME: tree_ir.ReadDictionaryKeysInstruction,
        cfg_ir.READ_DICT_VALUE_MACRO_NAME: tree_ir.ReadDictionaryValueInstruction,
        cfg_ir.READ_DICT_NODE_MACRO_NAME: tree_ir.ReadDictionaryNodeInstruction,
        cfg_ir.INDEX_MACRO_NAME: tree_ir.LoadIndexInstruction,
        cfg_ir.REVERSE_LIST_MACRO_NAME:
        lambda seq:
        tree_ir.ListSliceInstruction(seq, None, None, tree_ir.LiteralInstruction(-1)),
        cfg_ir.GC_PROTECT_MACRO_NAME: lower_gc_protect,
        cfg_ir.MAYBE_GC_PROTECT_MACRO_NAME: lower_maybe_gc_protect,
        cfg_ir.REGISTER_DEBUG_INFO_MACRO_NAME: lower_register_debug_info
    }

def lower_flow_graph(entry_point, jit):
    """Lowers the control-flow graph defined by the given entry point to tree IR."""
    cfg_lowerer = LoweringState(jit, find_inlinable_definitions(entry_point))
    ep_branches = entry_point.flow.branches()
    if len(ep_branches) == 0:
        lowered_body = cfg_lowerer.lower_block(entry_point)
    else:
        lowered_body = reloop_function_body(entry_point).lower(cfg_lowerer)
        lowered_body = tree_ir.CompoundInstruction(
            cfg_lowerer.lower_jump(cfg_ir.create_jump(entry_point)), lowered_body)
    return lowered_body
