Browse Source

Merge branch 'jit' of msdl.uantwerpen.be:jonathanvdc/modelverse into yentl

Yentl Van Tendeloo 8 years ago
parent
commit
44ab9d9be5

+ 40 - 15
hybrid_server/classes/mvkcontroller.xml

@@ -12,24 +12,49 @@
             self.root = self.mvs.read_root()[0]
             self.root = self.mvs.read_root()[0]
 
 
             # Instantiate the kernel.
             # Instantiate the kernel.
-            default_kernel_type = 'baseline-jit'
-            kernel_type = default_kernel_type
+            self.mvk = ModelverseKernel(self.root)
+
+            # Parse kernel-related options
+            default_kernel_type = 'adaptive-jit'
+            kernel_opts = [default_kernel_type]
             for parameter in params:
             for parameter in params:
                 if parameter.startswith('--kernel='):
                 if parameter.startswith('--kernel='):
-                    kernel_type = parameter[len('--kernel='):]
+                    kernel_opts = parameter[len('--kernel='):].split(',')
+
+            for opt in kernel_opts:
+                if opt == 'legacy-interpreter':
+                    self.mvk = LegacyModelverseKernel(self.root)
+                elif opt == 'interpreter':
+                    self.mvk.jit.set_jit_enabled(False)
+                elif opt == 'no-thunks':
+                    self.mvk.jit.enable_thunks(False)
+                elif opt == 'thunks':
+                    self.mvk.jit.enable_thunks()
+                elif opt == 'no-source-maps':
+                    self.mvk.jit.enable_source_maps(False)
+                elif opt == 'source-maps':
+                    self.mvk.jit.enable_source_maps()
+                elif opt == 'trace':
+                    self.mvk.jit.enable_tracing()
+                elif opt == 'fast-jit':
+                    self.mvk.jit.set_function_body_compiler(jit.compile_function_body_fast)
+                elif opt == 'baseline-jit':
+                    self.mvk.jit.set_function_body_compiler(jit.compile_function_body_baseline)
+                elif opt == 'adaptive-jit-favor-large-functions':
+                    self.mvk.jit.set_function_body_compiler(
+                            lambda *args: jit.compile_function_body_adaptive(
+                                *args, temperature_heuristic=jit.favor_large_functions))
+                elif opt == 'adaptive-jit-favor-small-functions':
+                    self.mvk.jit.set_function_body_compiler(
+                        lambda *args: jit.compile_function_body_adaptive(
+                            *args, temperature_heuristic=jit.favor_small_functions))
+                elif opt == 'adaptive-jit' or opt == 'adaptive-jit-favor-loops':
+                    self.mvk.jit.set_function_body_compiler(
+                        lambda *args: jit.compile_function_body_adaptive(
+                            *args, temperature_heuristic=jit.favor_loops))
+                else:
+                    print("warning: unknown kernel option '%s'." % opt)
 
 
-            if kernel_type == 'legacy-interpreter':
-                self.mvk = LegacyModelverseKernel(self.root)
-            elif kernel_type == 'interpreter':
-                self.mvk = ModelverseKernel(self.root)
-                self.mvk.jit.set_jit_enabled(False)
-            else:
-                if kernel_type != default_kernel_type:
-                    print(
-                        "warning: unknown kernel type '%s'. Defaulting to '%s'."
-                        % (kernel_type, default_kernel_type))
-                self.mvk = ModelverseKernel(self.root)
-                
             self.all_failed = False
             self.all_failed = False
             self.timeout = False
             self.timeout = False
             self.init_time = time.time()
             self.init_time = time.time()

+ 1 - 0
hybrid_server/server.xml

@@ -19,6 +19,7 @@
         from modelverse_kernel.legacy import ModelverseKernel as LegacyModelverseKernel
         from modelverse_kernel.legacy import ModelverseKernel as LegacyModelverseKernel
         from modelverse_state.main import ModelverseState
         from modelverse_state.main import ModelverseState
         #from modelverse_state.rdf import ModelverseState
         #from modelverse_state.rdf import ModelverseState
+        import modelverse_jit.jit as jit
     </top>
     </top>
 
 
     <inport name="socket_in"/>
     <inport name="socket_in"/>

+ 307 - 0
kernel/modelverse_jit/bytecode_ir.py

@@ -0,0 +1,307 @@
+"""Provides data structures that represent parsed Modelverse bytecode graphs."""
+
+class Instruction(object):
+    """Represents a Modelverse bytecode instruction."""
+    def __init__(self):
+        self.next_instruction = None
+        self.debug_information = None
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction, excluding the
+           next instruction."""
+        raise NotImplementedError()
+
+    def get_reachable(self):
+        """Gets the set of all instructions that are reachable from the given instruction, including
+           this instruction."""
+        results = set()
+        stack = [self]
+        while len(stack) > 0:
+            instr = stack.pop()
+            results.add(instr)
+            next_instr = instr.next_instruction
+            if next_instr is not None and next_instr not in results:
+                stack.append(next_instr)
+            for other in instr.get_directly_reachable():
+                if other not in results:
+                    assert other is not None
+                    stack.append(other)
+
+        return results
+
+class VariableNode(object):
+    """Represents a variable node, which has an identifier and an optional name."""
+    def __init__(self, node_id, name):
+        self.node_id = node_id
+        self.name = name
+
+    def __str__(self):
+        return 'var(%d, %s)' % (self.node_id, self.name)
+
+    def __repr__(self):
+        return 'VariableNode(%r, %r)' % (self.node_id, self.name)
+
+class SelectInstruction(Instruction):
+    """Represents an 'if/else' instruction."""
+    def __init__(self, condition, if_clause, else_clause):
+        Instruction.__init__(self)
+        self.condition = condition
+        self.if_clause = if_clause
+        self.else_clause = else_clause
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        if self.else_clause is None:
+            return (self.condition, self.if_clause)
+        else:
+            return (self.condition, self.if_clause, self.else_clause)
+
+    constructor_parameters = (
+        ('cond', Instruction),
+        ('then', Instruction),
+        ('else', Instruction))
+
+    def __repr__(self):
+        return '@%r: SelectInstruction(@%r, @%r, @%r)' % (
+            id(self), id(self.condition), id(self.if_clause), id(self.else_clause))
+
+class WhileInstruction(Instruction):
+    """Represents a 'while' instruction."""
+    def __init__(self, condition, body):
+        Instruction.__init__(self)
+        self.condition = condition
+        self.body = body
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return (self.condition, self.body)
+
+    constructor_parameters = (
+        ('cond', Instruction),
+        ('body', Instruction))
+
+    def __repr__(self):
+        return '@%r: WhileInstruction(@%r, @%r)' % (
+            id(self), id(self.condition), id(self.body))
+
+class BreakInstruction(Instruction):
+    """Represents a 'break' instruction."""
+    def __init__(self, loop):
+        Instruction.__init__(self)
+        self.loop = loop
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return (self.loop,)
+
+    constructor_parameters = (('while', WhileInstruction),)
+
+    def __repr__(self):
+        return '@%r: BreakInstruction(@%r)' % (
+            id(self), id(self.loop))
+
+class ContinueInstruction(Instruction):
+    """Represents a 'continue' instruction."""
+    def __init__(self, loop):
+        Instruction.__init__(self)
+        self.loop = loop
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return (self.loop,)
+
+    constructor_parameters = (('while', WhileInstruction),)
+
+    def __repr__(self):
+        return '@%r: ContinueInstruction(@%r)' % (
+            id(self), id(self.loop))
+
+class ReturnInstruction(Instruction):
+    """Represents a 'return' instruction, which terminates the current function
+       and optionally returns a value."""
+    def __init__(self, value):
+        Instruction.__init__(self)
+        self.value = value
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        if self.value is None:
+            return ()
+        else:
+            return (self.value,)
+
+    constructor_parameters = (('value', Instruction),)
+
+    def __repr__(self):
+        return '@%r: ReturnInstruction(@%r)' % (
+            id(self), id(self.value))
+
+class CallInstruction(Instruction):
+    """Represents a 'call' instruction, which calls a function with an argument
+       list, encoded as a list of name-instruction tuples."""
+    def __init__(self, target, argument_list):
+        Instruction.__init__(self)
+        self.target = target
+        self.argument_list = argument_list
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return (self.target,) + tuple((value for _, value in self.argument_list))
+
+    def __repr__(self):
+        return '@%r: CallInstruction(@%r, [%s])' % (
+            id(self), id(self.target),
+            ', '.join(['%s=@%r' % (name, id(value)) for name, value in self.argument_list]))
+
+class ConstantInstruction(Instruction):
+    """Represents a 'constant' instruction, which produces a reference
+       to a constant node."""
+    def __init__(self, constant_id):
+        Instruction.__init__(self)
+        self.constant_id = constant_id
+        assert self.constant_id is not None
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return ()
+
+    constructor_parameters = (('node', int),)
+
+    def __repr__(self):
+        return '@%r: ConstantInstruction(%r)' % (id(self), self.constant_id)
+
+class InputInstruction(Instruction):
+    """Represents an 'input' instruction, which pops a node from the input
+       queue."""
+    def __init__(self):
+        Instruction.__init__(self)
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return ()
+
+    constructor_parameters = ()
+
+    def __repr__(self):
+        return '@%r: InputInstruction()' % id(self)
+
+class OutputInstruction(Instruction):
+    """Represents an 'output' instruction, which pushes a node onto the output
+       queue."""
+    def __init__(self, value):
+        Instruction.__init__(self)
+        self.value = value
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return (self.value,)
+
+    constructor_parameters = (('value', Instruction),)
+
+    def __repr__(self):
+        return '@%r: OutputInstruction(@%r)' % (
+            id(self), id(self.value))
+
+class DeclareInstruction(Instruction):
+    """Represents a 'declare' instruction, which declares a local variable."""
+    def __init__(self, variable):
+        Instruction.__init__(self)
+        self.variable = variable
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return ()
+
+    constructor_parameters = (('var', VariableNode),)
+
+    def __repr__(self):
+        return '@%r: DeclareInstruction(%r)' % (
+            id(self), self.variable)
+
+class GlobalInstruction(Instruction):
+    """Represents a 'global' instruction, which declares a global variable."""
+    def __init__(self, variable):
+        Instruction.__init__(self)
+        self.variable = variable
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return ()
+
+    constructor_parameters = (('var', VariableNode),)
+
+    def __repr__(self):
+        return '@%r: GlobalInstruction(%r)' % (
+            id(self), self.variable)
+
+class ResolveInstruction(Instruction):
+    """Represents a 'resolve' instruction, which resolves a variable node/name as
+       either a local or global variable."""
+    def __init__(self, variable):
+        Instruction.__init__(self)
+        self.variable = variable
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return ()
+
+    constructor_parameters = (('var', VariableNode),)
+
+    def __repr__(self):
+        return '@%r: ResolveInstruction(%r)' % (
+            id(self), self.variable)
+
+class AccessInstruction(Instruction):
+    """Represents an 'access' instruction, which loads the node pointed to by a
+       pointer node."""
+    def __init__(self, pointer):
+        Instruction.__init__(self)
+        self.pointer = pointer
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return (self.pointer,)
+
+    constructor_parameters = (('var', Instruction),)
+
+    def __repr__(self):
+        return '@%r: AccessInstruction(@%r)' % (
+            id(self), id(self.pointer))
+
+class AssignInstruction(Instruction):
+    """Represents an 'assign' instruction, which sets the node pointed to by a
+       pointer node to the given node."""
+    def __init__(self, pointer, value):
+        Instruction.__init__(self)
+        self.pointer = pointer
+        self.value = value
+
+    def get_directly_reachable(self):
+        """Gets all instructions that are directly reachable from this instruction."""
+        return (self.pointer, self.value)
+
+    constructor_parameters = (
+        ('var', Instruction),
+        ('value', Instruction))
+
+    def __repr__(self):
+        return '@%r: AssignInstruction(@%r, @%r)' % (
+            id(self), id(self.pointer), id(self.value))
+
+INSTRUCTION_TYPE_MAPPING = {
+    'if' : SelectInstruction,
+    'while' : WhileInstruction,
+    'return' : ReturnInstruction,
+    'constant' : ConstantInstruction,
+    'resolve' : ResolveInstruction,
+    'declare' : DeclareInstruction,
+    'global' : GlobalInstruction,
+    'assign' : AssignInstruction,
+    'access' : AccessInstruction,
+    'output' : OutputInstruction,
+    'input' : InputInstruction,
+    'call' : CallInstruction,
+    'break' : BreakInstruction,
+    'continue' : ContinueInstruction
+}
+"""Maps instruction names to types."""

+ 117 - 0
kernel/modelverse_jit/bytecode_parser.py

@@ -0,0 +1,117 @@
+"""Parses Modelverse bytecode graphs into bytecode ir."""
+
+import modelverse_jit.bytecode_ir as bytecode_ir
+import modelverse_jit.runtime as jit_runtime
+import modelverse_kernel.primitives as primitive_functions
+
+class BytecodeParser(object):
+    """Parses bytecode graphs."""
+    def __init__(self):
+        self.parsed_nodes = {}
+
+    def parse_instruction(self, node_id):
+        """Parses the instruction node with the given identifier."""
+        if node_id is None:
+            raise primitive_functions.PrimitiveFinished(None)
+        elif node_id in self.parsed_nodes:
+            # We've already parsed this node, so return it right away.
+            raise primitive_functions.PrimitiveFinished(self.parsed_nodes[node_id])
+
+        instruction_val, = yield [("RV", [node_id])]
+        instruction_type = instruction_val["value"]
+        # Create an instruction and store it in the instruction dictionary.
+        instruction = self.create_instruction(instruction_type)
+        self.parsed_nodes[node_id] = instruction
+        # Initialize the instruction from the node's data.
+        yield [("CALL_ARGS", [self.initialize_instruction, (instruction, node_id)])]
+        # Retrieve the debug information.
+        debug_info, = yield [("RD", [node_id, "__debug"])]
+        if debug_info is not None:
+            debug_info, = yield [("RV", [debug_info])]
+            instruction.debug_information = debug_info
+
+        # Check if the instruction has a 'next' instruction.
+        next_instr_id, = yield [("RD", [node_id, "next"])]
+        if next_instr_id is not None:
+            instruction.next_instruction, = yield [
+                ("CALL_ARGS", [self.parse_instruction, (next_instr_id,)])]
+
+        raise primitive_functions.PrimitiveFinished(instruction)
+
+    def parse_variable(self, node_id):
+        """Parses the given variable node."""
+        var_name, = yield [("RV", [node_id])]
+        raise primitive_functions.PrimitiveFinished(
+            bytecode_ir.VariableNode(node_id, var_name))
+
+    def __parse_node_unchecked(self, node_id, result_type):
+        """Parses the given node as the specified type of object, without
+           checking that the result actually conforms to said type."""
+        if result_type is bytecode_ir.VariableNode:
+            yield [("TAIL_CALL_ARGS", [self.parse_variable, (node_id,)])]
+        elif result_type is int:
+            raise primitive_functions.PrimitiveFinished(node_id)
+        else:
+            yield [("TAIL_CALL_ARGS", [self.parse_instruction, (node_id,)])]
+
+    def parse_node(self, node_id, result_type):
+        """Parses the given node as the specified type of object."""
+        result, = yield [("CALL_ARGS", [self.__parse_node_unchecked, (node_id, result_type)])]
+        if result is not None and not isinstance(result, result_type):
+            raise jit_runtime.JitCompilationFailedException(
+                "Parsed a node as an instance of '%s', expected an instance of '%s'." % (
+                    type(result).__name__, result_type.__name__))
+
+        raise primitive_functions.PrimitiveFinished(result)
+
+    def parse_arguments(self, first_argument_id):
+        """Parses the parameter-to-argument mapping started by the specified first argument
+           node."""
+        next_param = first_argument_id
+        named_args = []
+        while next_param is not None:
+            param_name_id, = yield [("RD", [next_param, "name"])]
+            param_name, = yield [("RV", [param_name_id])]
+            param_val_id, = yield [("RD", [next_param, "value"])]
+            param_val, = yield [("CALL_ARGS", [self.parse_instruction, (param_val_id,)])]
+            named_args.append((param_name, param_val))
+
+            next_param, = yield [("RD", [next_param, "next_param"])]
+
+        raise primitive_functions.PrimitiveFinished(named_args)
+
+    def create_instruction(self, instruction_type):
+        """Creates an instruction of the given type."""
+        if instruction_type in bytecode_ir.INSTRUCTION_TYPE_MAPPING:
+            return object.__new__(bytecode_ir.INSTRUCTION_TYPE_MAPPING[instruction_type])
+        else:
+            raise jit_runtime.JitCompilationFailedException(
+                "Unknown instruction type: '%s'" % instruction_type)
+
+    def initialize_call(self, call_instruction, node_id):
+        """Initializes the given call instruction."""
+        func_id, first_arg_id, = yield [
+            ("RD", [node_id, "func"]),
+            ("RD", [node_id, "params"])]
+        func_val, = yield [("CALL_ARGS", [self.parse_instruction, (func_id,)])]
+        named_args, = yield [("CALL_ARGS", [self.parse_arguments, (first_arg_id,)])]
+        call_instruction.__init__(func_val, named_args)
+        raise primitive_functions.PrimitiveFinished(None)
+
+    def initialize_instruction(self, instruction, node_id):
+        """Initializes the given instruction with data from the given node."""
+        instr_type = type(instruction)
+        # print("Initializing '%s' node" % instr_type)
+        if instr_type is bytecode_ir.CallInstruction:
+            # Call instructions are complicated, so they get special treatment.
+            yield [("TAIL_CALL_ARGS", [self.initialize_call, (instruction, node_id)])]
+        else:
+            # Construct an argument list based on the `constructor_parameters` attribute
+            # of the instruction type.
+            arg_list = []
+            for dict_key, ctor_param_ty in instr_type.constructor_parameters:
+                arg_id, = yield [("RD", [node_id, dict_key])]
+                arg, = yield [("CALL_ARGS", [self.parse_node, (arg_id, ctor_param_ty)])]
+                arg_list.append(arg)
+            instruction.__init__(*arg_list)
+            raise primitive_functions.PrimitiveFinished(None)

+ 368 - 0
kernel/modelverse_jit/bytecode_to_cfg.py

@@ -0,0 +1,368 @@
+"""Converts bytecode IR to CFG IR."""
+
+import modelverse_jit.bytecode_ir as bytecode_ir
+import modelverse_jit.cfg_ir as cfg_ir
+import modelverse_jit.runtime as jit_runtime
+
+def emit_debug_info_trace(block, debug_info, function_name):
+    """Appends a tracing instruction to the given block that prints
+       the given debug information and function name."""
+    if debug_info is not None or function_name is not None:
+        block.append_definition(
+            cfg_ir.create_print(
+                block.append_definition(
+                    cfg_ir.Literal(jit_runtime.format_trace_message(
+                        debug_info, function_name, jit_runtime.FAST_JIT_ORIGIN_NAME)))))
+
+class AnalysisState(object):
+    """State that is common to the bytecode->CFG transformation of a function."""
+    def __init__(self, jit, function_name, param_dict):
+        self.jit = jit
+        self.function_name = function_name
+        self.counter = cfg_ir.SharedCounter()
+        self.analyzed_instructions = set()
+        self.loop_instructions = {}
+        self.entry_point = cfg_ir.BasicBlock(self.counter)
+        self.current_block = self.entry_point
+        self.root_node = None
+        self.__write_prolog(param_dict)
+
+    def __write_prolog(self, param_dict):
+        # Write a prolog in CFG IR.
+        # We want to create the following definition:
+        #
+        #     !entry_point():
+        #         static if self.jit.source_maps_enabled:
+        #             $function_name = literal <function map>
+        #             $source_map_name = literal <source map name>
+        #             $origin_name = literal jit_runtime.FAST_JIT_ORIGIN_NAME
+        #             $_ = direct-call ('macro-positional', void) register_debug_info(
+        #                 function_name=$functionsource_map=$source_map_name)
+        #
+        #         $jit_locals = alloc-root-node
+        #         $_ = declare-local var(...)
+        #         $param_1 = resolve-local var(...)
+        #         $arg_1 = function-parameter ...
+        #         $_ = store $param_1, $arg_1
+        #         ...
+        #
+        #         static if self.jit.nop_insertion_enabled:
+        #             $_ = direct-call ('macro-io', void) nop()
+        #
+        # We also want to store '$jit_locals' in an attribute, so we can
+        # use it to shield locals from the GC.
+        if self.jit.source_maps_enabled:
+            function_name = self.current_block.append_definition(
+                cfg_ir.Literal(self.function_name))
+            source_map_name = self.current_block.append_definition(
+                cfg_ir.Literal(self.jit.get_source_map_name(self.function_name)))
+            origin_name = self.current_block.append_definition(
+                cfg_ir.Literal(jit_runtime.FAST_JIT_ORIGIN_NAME))
+            self.current_block.append_definition(
+                cfg_ir.DirectFunctionCall(
+                    cfg_ir.REGISTER_DEBUG_INFO_MACRO_NAME,
+                    [('function_name', function_name),
+                     ('source_map', source_map_name),
+                     ('origin_name', origin_name)],
+                    calling_convention=cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION,
+                    has_value=False))
+
+        self.root_node = self.current_block.append_definition(cfg_ir.AllocateRootNode())
+        for node_id, name in param_dict.items():
+            variable = bytecode_ir.VariableNode(node_id, name)
+            self.current_block.append_definition(cfg_ir.DeclareLocal(variable, self.root_node))
+            param_i = self.current_block.append_definition(cfg_ir.ResolveLocal(variable))
+            arg_i = self.current_block.append_definition(cfg_ir.FunctionParameter(name))
+            self.current_block.append_definition(cfg_ir.StoreAtPointer(param_i, arg_i))
+
+        if self.jit.nop_insertion_enabled:
+            self.current_block.append_definition(cfg_ir.create_nop())
+
+    def analyze(self, instruction):
+        """Analyzes the given instruction as a basic block."""
+        if instruction in self.analyzed_instructions:
+            raise jit_runtime.JitCompilationFailedException(
+                'Cannot jit non-tree instruction graph.')
+
+        self.analyzed_instructions.add(instruction)
+
+        # Find an analyzer.
+        instruction_type = type(instruction)
+        if instruction_type in self.instruction_analyzers:
+            if self.jit.tracing_enabled:
+                emit_debug_info_trace(
+                    self.current_block, instruction.debug_information, self.function_name)
+
+            # Analyze the instruction.
+            result = self.instruction_analyzers[instruction_type](self, instruction)
+
+            if self.jit.source_maps_enabled:
+                result.debug_information = instruction.debug_information
+
+            # Check if the instruction has a 'next' instruction. If so, analyze it!
+            if instruction.next_instruction is not None:
+                next_result = self.analyze(instruction.next_instruction)
+                if next_result.value.has_value() or (not result.value.has_value()):
+                    result = next_result
+
+            return result
+        else:
+            raise jit_runtime.JitCompilationFailedException(
+                "Unknown instruction type: '%s'" % type(instruction))
+
+    def emit_select(self, create_condition, create_if_body, create_else_body):
+        """Emits a 'select' instruction."""
+        # Create blocks that look like this:
+        #
+        # !current_block(...):
+        #     ...
+        #     $cond = <condition>
+        #     select $cond, !if_block(), !else_block()
+        #
+        # !if_block():
+        #     $if_result = <if-body>
+        #     jump !phi_block($if_result)
+        #
+        # !else_block():
+        #     $else_result = <else-body>
+        #     jump !phi_block($else_result)
+        #
+        # !phi_block($result = block-parameter):
+        #     ...
+        #
+        if_block = cfg_ir.BasicBlock(self.counter)
+        else_block = cfg_ir.BasicBlock(self.counter)
+        phi_block = cfg_ir.BasicBlock(self.counter)
+        param_def = phi_block.append_parameter(cfg_ir.BlockParameter())
+
+        condition = create_condition()
+        self.current_block.flow = cfg_ir.SelectFlow(
+            condition, cfg_ir.Branch(if_block), cfg_ir.Branch(else_block))
+
+        self.current_block = if_block
+        if_result = create_if_body()
+        self.current_block.flow = cfg_ir.create_jump(phi_block, [if_result])
+
+        self.current_block = else_block
+        else_result = create_else_body()
+        self.current_block.flow = cfg_ir.create_jump(phi_block, [else_result])
+
+        self.current_block = phi_block
+        return param_def
+
+    def analyze_if(self, instruction):
+        """Analyzes an 'if' instruction."""
+        def __analyze_condition():
+            condition_node = self.analyze(instruction.condition)
+            return self.current_block.append_definition(cfg_ir.Read(condition_node))
+        return self.emit_select(
+            __analyze_condition,
+            lambda: self.analyze(instruction.if_clause),
+            lambda:
+            self.current_block.append_definition(cfg_ir.Literal(None))
+            if instruction.else_clause is None
+            else self.analyze(instruction.else_clause))
+
+    def analyze_while(self, instruction):
+        """Analyzes a 'while' instruction."""
+        # Create blocks that look like this:
+        #
+        # !current_block(...):
+        #     ...
+        #     jump !loop_condition()
+        #
+        # !loop_condition():
+        #     $condition_node = <condition>
+        #     $condition = read condition_node
+        #     select $condition, !loop_body(), !loop_exit()
+        #
+        # !loop_body():
+        #     $result = <body>
+        #     static if jit.nop_insertion_enabled:
+        #         $_ = direct-call ('macro-io', void) nop()
+        #     jump !loop_condition()
+        #
+        # !loop_exit():
+        #     $nothing = literal None
+        #     ...
+        loop_condition_block = cfg_ir.BasicBlock(self.counter)
+        loop_body_block = cfg_ir.BasicBlock(self.counter)
+        loop_exit_block = cfg_ir.BasicBlock(self.counter)
+        self.loop_instructions[instruction] = (loop_condition_block, loop_exit_block)
+
+        self.current_block.flow = cfg_ir.create_jump(loop_condition_block)
+
+        self.current_block = loop_condition_block
+        condition_node = self.analyze(instruction.condition)
+        condition = self.current_block.append_definition(cfg_ir.Read(condition_node))
+        self.current_block.flow = cfg_ir.SelectFlow(
+            condition, cfg_ir.Branch(loop_body_block), cfg_ir.Branch(loop_exit_block))
+
+        self.current_block = loop_body_block
+        self.analyze(instruction.body)
+        if self.jit.nop_insertion_enabled:
+            self.current_block.append_definition(cfg_ir.create_nop())
+        self.current_block.flow = cfg_ir.create_jump(loop_condition_block)
+
+        self.current_block = loop_exit_block
+        return loop_exit_block.append_definition(cfg_ir.Literal(None))
+
+    def analyze_return(self, instruction):
+        """Analyzes a 'return' instruction."""
+        if instruction.value is None:
+            return_value = self.current_block.append_definition(cfg_ir.Literal(None))
+        else:
+            return_value = self.analyze(instruction.value)
+        # Don't forget to deallocate the root node.
+        self.current_block.append_definition(cfg_ir.DeallocateRootNode(self.root_node))
+        self.current_block.flow = cfg_ir.ReturnFlow(return_value)
+        self.current_block = cfg_ir.BasicBlock(self.counter)
+        return self.current_block.append_definition(cfg_ir.Literal(None))
+
+    def analyze_constant(self, instruction):
+        """Analyzes a 'constant' instruction."""
+        return self.current_block.append_definition(cfg_ir.Literal(instruction.constant_id))
+
+    def analyze_resolve(self, instruction):
+        """Analyzes a 'resolve' instruction."""
+        def __resolve_global_carefully():
+            # We might be resolving a global that does not exist. In that case, we
+            # need to throw. We want to create the following blocks:
+            #
+            # !current_block(...):
+            #     ...
+            #     $resolved_global = resolve-global global
+            #     $nothing = literal None
+            #     $condition = binary $resolved_global, 'is', $nothing
+            #     select $condition, !no_global_block(), !global_exists_block()
+            #
+            # !no_global_block():
+            #     $message = literal <GLOBAL_NOT_FOUND_MESSAGE_FORMAT % instruction.variable.name>
+            #     $exception = direct-call "simple-positional" Exception(message=$message)
+            #     throw $exception
+            #
+            # !global_exists_block():
+            #     ...
+            #
+            no_global_block = cfg_ir.BasicBlock(self.counter)
+            global_exists_block = cfg_ir.BasicBlock(self.counter)
+
+            resolved_global = self.current_block.append_definition(
+                cfg_ir.ResolveGlobal(instruction.variable))
+            nothing = self.current_block.append_definition(cfg_ir.Literal(None))
+            condition = self.current_block.append_definition(
+                cfg_ir.Binary(resolved_global, 'is', nothing))
+            self.current_block.flow = cfg_ir.SelectFlow(
+                condition, cfg_ir.Branch(no_global_block), cfg_ir.Branch(global_exists_block))
+
+            message = no_global_block.append_definition(
+                cfg_ir.Literal(
+                    jit_runtime.GLOBAL_NOT_FOUND_MESSAGE_FORMAT % instruction.variable.name))
+            exception = no_global_block.append_definition(
+                cfg_ir.DirectFunctionCall(
+                    'Exception',
+                    [('message', message)],
+                    cfg_ir.SIMPLE_POSITIONAL_CALLING_CONVENTION))
+            no_global_block.flow = cfg_ir.ThrowFlow(exception)
+
+            self.current_block = global_exists_block
+            return resolved_global
+        return self.emit_select(
+            lambda:
+            self.current_block.append_definition(
+                cfg_ir.CheckLocalExists(instruction.variable)),
+            lambda:
+            self.current_block.append_definition(
+                cfg_ir.ResolveLocal(instruction.variable)),
+            __resolve_global_carefully)
+
+    def analyze_declare(self, instruction):
+        """Analyzes a 'declare' instruction."""
+        return self.current_block.append_definition(
+            cfg_ir.DeclareLocal(instruction.variable, self.root_node))
+
+    def analyze_global(self, instruction):
+        """Analyzes a 'global' instruction."""
+        resolved_global = self.current_block.append_definition(
+            cfg_ir.ResolveGlobal(instruction.variable))
+        nothing = self.current_block.append_definition(cfg_ir.Literal(None))
+        return self.emit_select(
+            lambda:
+            self.current_block.append_definition(
+                cfg_ir.Binary(resolved_global, 'is', nothing)),
+            lambda:
+            self.current_block.append_definition(
+                cfg_ir.DeclareGlobal(instruction.variable)),
+            lambda: resolved_global)
+
+    def analyze_assign(self, instruction):
+        """Analyzes an 'assign' instruction."""
+        pointer_result = self.analyze(instruction.pointer)
+        value_result = self.analyze(instruction.value)
+        return self.current_block.append_definition(
+            cfg_ir.StoreAtPointer(pointer_result, value_result))
+
+    def analyze_access(self, instruction):
+        """Analyzes an 'access' instruction."""
+        pointer_result = self.analyze(instruction.pointer)
+        return self.current_block.append_definition(cfg_ir.LoadPointer(pointer_result))
+
+    def analyze_output(self, instruction):
+        """Analyzes an 'output' instruction."""
+        value_result = self.analyze(instruction.value)
+        return self.current_block.append_definition(cfg_ir.create_output(value_result))
+
+    def analyze_input(self, _):
+        """Analyzes an 'input' instruction."""
+        return self.current_block.append_definition(cfg_ir.create_input())
+
+    def analyze_break(self, instruction):
+        """Analyzes a 'break' instruction."""
+        if instruction.loop not in self.loop_instructions:
+            raise jit_runtime.JitCompilationFailedException(
+                "'break' instruction targets a 'while' loop that has not been defined yet.")
+
+        _, exit_block = self.loop_instructions[instruction.loop]
+        self.current_block.flow = cfg_ir.create_jump(exit_block)
+        self.current_block = cfg_ir.BasicBlock(self.counter)
+        return self.current_block.append_definition(cfg_ir.Literal(None))
+
+    def analyze_continue(self, instruction):
+        """Analyzes a 'continue' instruction."""
+        if instruction.loop not in self.loop_instructions:
+            raise jit_runtime.JitCompilationFailedException(
+                "'continue' instruction targets a 'while' loop that has not been defined yet.")
+
+        if self.jit.nop_insertion_enabled:
+            self.current_block.append_definition(cfg_ir.create_nop())
+        cond_block, _ = self.loop_instructions[instruction.loop]
+        self.current_block.flow = cfg_ir.create_jump(cond_block)
+        self.current_block = cfg_ir.BasicBlock(self.counter)
+        return self.current_block.append_definition(cfg_ir.Literal(None))
+
+    def analyze_call(self, instruction):
+        """Analyzes the given 'call' instruction."""
+        target = self.analyze(instruction.target)
+        arg_list = []
+        for key, arg_instruction in instruction.argument_list:
+            arg_list.append((key, self.analyze(arg_instruction)))
+
+        return self.current_block.append_definition(cfg_ir.IndirectFunctionCall(target, arg_list))
+
+    instruction_analyzers = {
+        bytecode_ir.SelectInstruction : analyze_if,
+        bytecode_ir.WhileInstruction : analyze_while,
+        bytecode_ir.ReturnInstruction : analyze_return,
+        bytecode_ir.ConstantInstruction : analyze_constant,
+        bytecode_ir.ResolveInstruction : analyze_resolve,
+        bytecode_ir.DeclareInstruction : analyze_declare,
+        bytecode_ir.GlobalInstruction : analyze_global,
+        bytecode_ir.AssignInstruction : analyze_assign,
+        bytecode_ir.AccessInstruction : analyze_access,
+        bytecode_ir.OutputInstruction : analyze_output,
+        bytecode_ir.InputInstruction : analyze_input,
+        bytecode_ir.CallInstruction : analyze_call,
+        bytecode_ir.BreakInstruction : analyze_break,
+        bytecode_ir.ContinueInstruction : analyze_continue
+    }
+

+ 720 - 0
kernel/modelverse_jit/bytecode_to_tree.py

@@ -0,0 +1,720 @@
+"""Naively converts bytecode IR to tree IR."""
+
+import modelverse_jit.bytecode_ir as bytecode_ir
+import modelverse_jit.tree_ir as tree_ir
+import modelverse_jit.runtime as jit_runtime
+import modelverse_kernel.primitives as primitive_functions
+
+def get_parameter_names(compiled_function):
+    """Gets the given compiled function's parameter names."""
+    if hasattr(compiled_function, '__code__'):
+        return compiled_function.__code__.co_varnames[
+            :compiled_function.__code__.co_argcount]
+    elif hasattr(compiled_function, '__init__'):
+        return get_parameter_names(compiled_function.__init__)[1:]
+    else:
+        raise ValueError("'compiled_function' must be a function or a type.")
+
+def apply_intrinsic(intrinsic_function, named_args):
+    """Applies the given intrinsic to the given sequence of named arguments."""
+    param_names = get_parameter_names(intrinsic_function)
+    if tuple(param_names) == tuple([n for n, _ in named_args]):
+        # Perfect match. Yay!
+        return intrinsic_function(**dict(named_args))
+    else:
+        # We'll have to store the arguments into locals to preserve
+        # the order of evaluation.
+        stored_args = [(name, tree_ir.StoreLocalInstruction(None, arg)) for name, arg in named_args]
+        arg_value_dict = dict([(name, arg.create_load()) for name, arg in stored_args])
+        store_instructions = [instruction for _, instruction in stored_args]
+        return tree_ir.CompoundInstruction(
+            tree_ir.create_block(*store_instructions),
+            intrinsic_function(**arg_value_dict))
+
+def retrieve_task_root():
+    """Creates an instruction that stores the task_root variable in a local."""
+    return tree_ir.StoreLocalInstruction(
+        'task_root', load_task_root())
+
+def load_task_root():
+    """Creates an instruction that loads the task_root variable."""
+    return tree_ir.LoadIndexInstruction(
+        tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
+        tree_ir.LiteralInstruction('task_root'))
+
+def load_kernel():
+    """Creates an instruction that loads the Modelverse kernel."""
+    return tree_ir.LoadIndexInstruction(
+        tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
+        tree_ir.LiteralInstruction('mvk'))
+
+def create_access(pointer):
+    """Creates a tree that loads the given pointer's value."""
+    # Accessing a variable is pretty easy. It really just boils
+    # down to reading the value corresponding to the 'value' key
+    # of the variable.
+    #
+    #     value, = yield [("RD", [returnvalue, "value"])]
+    #
+    return tree_ir.ReadDictionaryValueInstruction(
+        pointer,
+        tree_ir.LiteralInstruction('value'))
+
+def create_assign(pointer, value):
+    """Creates a tree that assigns the given value to the given pointer."""
+    # Assignments work like this:
+    #
+    #     value_link = yield [("RDE", [variable, "value"])]
+    #     _, _ =       yield [("CD", [variable, "value", value]),
+    #                         ("DE", [value_link])]
+    #
+    variable = tree_ir.StoreLocalInstruction(None, pointer)
+    value = tree_ir.StoreLocalInstruction(None, value)
+    value_link = tree_ir.StoreLocalInstruction(
+        'value_link',
+        tree_ir.ReadDictionaryEdgeInstruction(
+            variable.create_load(),
+            tree_ir.LiteralInstruction('value')))
+
+    return tree_ir.create_block(
+        variable,
+        value,
+        value_link,
+        tree_ir.CreateDictionaryEdgeInstruction(
+            variable.create_load(),
+            tree_ir.LiteralInstruction('value'),
+            value.create_load()),
+        tree_ir.DeleteEdgeInstruction(
+            value_link.create_load()))
+
+def create_input(use_input_function=False):
+    """Creates an instruction that pops a value from the input queue."""
+    # Possible alternative to the explicit syntax tree: just call the jit_runtime.__get_input
+    # function.
+    if use_input_function:
+        return tree_ir.create_jit_call(
+            tree_ir.LoadGlobalInstruction(jit_runtime.GET_INPUT_FUNCTION_NAME),
+            [],
+            tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME))
+
+    # The plan is to generate this tree:
+    #
+    #     value = None
+    #     while True:
+    #         _input = yield [("RD", [task_root, "input"])]
+    #         value = yield [("RD", [_input, "value"])]
+    #
+    #         if value is None:
+    #             kwargs['mvk'].success = False # to avoid blocking
+    #             yield None # nop/interrupt
+    #         else:
+    #             break
+    #
+    #     _next = yield [("RD", [_input, "next"])]
+    #     yield [("CD", [task_root, "input", _next])]
+    #     yield [("CE", [jit_locals, value])]
+    #     yield [("DN", [_input])]
+
+    task_root = retrieve_task_root()
+    _input = tree_ir.StoreLocalInstruction(
+        None,
+        tree_ir.ReadDictionaryValueInstruction(
+            task_root.create_load(),
+            tree_ir.LiteralInstruction('input')))
+
+    value = tree_ir.StoreLocalInstruction(
+        None,
+        tree_ir.ReadDictionaryValueInstruction(
+            _input.create_load(),
+            tree_ir.LiteralInstruction('value')))
+
+    raise primitive_functions.PrimitiveFinished(
+        tree_ir.CompoundInstruction(
+            tree_ir.create_block(
+                task_root,
+                value.create_store(tree_ir.LiteralInstruction(None)),
+                tree_ir.LoopInstruction(
+                    tree_ir.create_block(
+                        _input,
+                        value,
+                        tree_ir.SelectInstruction(
+                            tree_ir.BinaryInstruction(
+                                value.create_load(),
+                                'is',
+                                tree_ir.LiteralInstruction(None)),
+                            tree_ir.create_block(
+                                tree_ir.StoreMemberInstruction(
+                                    load_kernel(),
+                                    'success',
+                                    tree_ir.LiteralInstruction(False)),
+                                tree_ir.NopInstruction()),
+                            tree_ir.BreakInstruction()))),
+                tree_ir.CreateDictionaryEdgeInstruction(
+                    task_root.create_load(),
+                    tree_ir.LiteralInstruction('input'),
+                    tree_ir.ReadDictionaryValueInstruction(
+                        _input.create_load(),
+                        tree_ir.LiteralInstruction('next'))),
+                tree_ir.CreateEdgeInstruction(
+                    tree_ir.LoadLocalInstruction(jit_runtime.LOCALS_NODE_NAME),
+                    value.create_load()),
+                tree_ir.DeleteNodeInstruction(_input.create_load())),
+            value.create_load()))
+
+def create_output(output_value):
+    """Creates an output instruction that outputs the given value."""
+    # The plan is to basically generate this tree:
+    #
+    # value = <some tree>
+    # last_output, last_output_link, new_last_output = \
+    #                 yield [("RD", [task_root, "last_output"]),
+    #                        ("RDE", [task_root, "last_output"]),
+    #                        ("CN", []),
+    #                       ]
+    # _, _, _, _ = \
+    #                 yield [("CD", [last_output, "value", value]),
+    #                        ("CD", [last_output, "next", new_last_output]),
+    #                        ("CD", [task_root, "last_output", new_last_output]),
+    #                        ("DE", [last_output_link])
+    #                       ]
+    # yield None
+
+    value_local = tree_ir.StoreLocalInstruction('value', output_value)
+
+    store_task_root = retrieve_task_root()
+    last_output = tree_ir.StoreLocalInstruction(
+        'last_output',
+        tree_ir.ReadDictionaryValueInstruction(
+            store_task_root.create_load(),
+            tree_ir.LiteralInstruction('last_output')))
+
+    last_output_link = tree_ir.StoreLocalInstruction(
+        'last_output_link',
+        tree_ir.ReadDictionaryEdgeInstruction(
+            store_task_root.create_load(),
+            tree_ir.LiteralInstruction('last_output')))
+
+    new_last_output = tree_ir.StoreLocalInstruction(
+        'new_last_output',
+        tree_ir.CreateNodeInstruction())
+
+    return tree_ir.create_block(
+        value_local,
+        store_task_root,
+        last_output,
+        last_output_link,
+        new_last_output,
+        tree_ir.CreateDictionaryEdgeInstruction(
+            last_output.create_load(),
+            tree_ir.LiteralInstruction('value'),
+            value_local.create_load()),
+        tree_ir.CreateDictionaryEdgeInstruction(
+            last_output.create_load(),
+            tree_ir.LiteralInstruction('next'),
+            new_last_output.create_load()),
+        tree_ir.CreateDictionaryEdgeInstruction(
+            store_task_root.create_load(),
+            tree_ir.LiteralInstruction('last_output'),
+            new_last_output.create_load()),
+        tree_ir.DeleteEdgeInstruction(last_output_link.create_load()),
+        tree_ir.NopInstruction())
+
+def create_indirect_call(target, argument_list):
+    """Creates an indirect call to the function defined by the node with the id computed
+       by the first argument."""
+    # Call the __call_function function to run the interpreter, like so:
+    #
+    # __call_function(function_id, { first_param_name : first_param_val, ... }, **kwargs)
+    #
+    dict_literal = tree_ir.DictionaryLiteralInstruction(
+        [(tree_ir.LiteralInstruction(key), val) for key, val in argument_list])
+    return tree_ir.create_jit_call(
+        tree_ir.LoadGlobalInstruction(jit_runtime.CALL_FUNCTION_NAME),
+        [('function_id', target), ('named_arguments', dict_literal)],
+        tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME))
+
+def with_debug_info_trace(instruction, debug_info, function_name):
+    """Prepends the given instruction with a tracing instruction that prints
+       the given debug information and function name."""
+    if debug_info is None and function_name is None:
+        return instruction
+    else:
+        return tree_ir.create_block(
+            tree_ir.PrintInstruction(
+                tree_ir.LiteralInstruction(
+                    jit_runtime.format_trace_message(
+                        debug_info, function_name,
+                        jit_runtime.BASELINE_JIT_ORIGIN_NAME))),
+            instruction)
+
+class LocalNameMap(object):
+    """A map that converts local variable nodes to identifiers."""
+    def __init__(self, local_mapping=None):
+        if local_mapping is None:
+            local_mapping = {}
+        self.local_mapping = local_mapping
+
+    def get_local_name(self, local_variable_id):
+        """Gets the name for the local variable node with the given id."""
+        if local_variable_id not in self.local_mapping:
+            self.local_mapping[local_variable_id] = 'local%d' % local_variable_id
+        return self.local_mapping[local_variable_id]
+
+class AnalysisState(object):
+    """The state of a bytecode analysis call graph."""
+    def __init__(self, jit, body_id, task_root, local_mapping, max_instructions=None):
+        self.analyzed_instructions = set()
+        self.function_vars = set()
+        self.local_vars = set()
+        self.body_id = body_id
+        self.max_instructions = max_instructions
+        self.task_root = task_root
+        self.jit = jit
+        self.local_name_map = LocalNameMap(local_mapping)
+        self.function_name = jit.jitted_entry_points[body_id]
+        self.enclosing_loop_instruction = None
+
+    def register_local_var(self, local_id):
+        """Registers the given variable node id as a local."""
+        if local_id in self.function_vars:
+            raise jit_runtime.JitCompilationFailedException(
+                "Local is used as target of function call.")
+        self.local_vars.add(local_id)
+
+    def register_function_var(self, local_id):
+        """Registers the given variable node id as a function."""
+        if local_id in self.local_vars:
+            raise jit_runtime.JitCompilationFailedException(
+                "Local is used as target of function call.")
+        self.function_vars.add(local_id)
+
+    def analyze(self, instruction):
+        """Tries to build an intermediate representation from the instruction with the
+        given id."""
+        # Check the analyzed_instructions set for instruction_id to avoid
+        # infinite loops.
+        if instruction in self.analyzed_instructions:
+            raise jit_runtime.JitCompilationFailedException(
+                'Cannot jit non-tree instruction graph.')
+        elif (self.max_instructions is not None and
+              len(self.analyzed_instructions) > self.max_instructions):
+            raise jit_runtime.JitCompilationFailedException(
+                'Maximum number of instructions exceeded.')
+
+        self.analyzed_instructions.add(instruction)
+        instruction_type = type(instruction)
+        if instruction_type in self.instruction_analyzers:
+            # Analyze the instruction itself.
+            outer_result, = yield [
+                ("CALL_ARGS", [self.instruction_analyzers[instruction_type], (self, instruction)])]
+            if instruction.debug_information is not None:
+                if self.jit.tracing_enabled:
+                    outer_result = with_debug_info_trace(
+                        outer_result, instruction.debug_information, self.function_name)
+                if self.jit.source_maps_enabled:
+                    outer_result = tree_ir.DebugInfoInstruction(
+                        outer_result, instruction.debug_information)
+
+            # Check if the instruction has a 'next' instruction.
+            if instruction.next_instruction is None:
+                raise primitive_functions.PrimitiveFinished(outer_result)
+            else:
+                next_result, = yield [
+                    ("CALL_ARGS", [self.analyze, (instruction.next_instruction,)])]
+                raise primitive_functions.PrimitiveFinished(
+                    tree_ir.CompoundInstruction(
+                        outer_result,
+                        next_result))
+        else:
+            raise jit_runtime.JitCompilationFailedException(
+                "Unknown instruction type: '%s'" % type(instruction))
+
+    def analyze_all(self, instruction_ids):
+        """Tries to compile a list of IR trees from the given list of instruction ids."""
+        results = []
+        for inst in instruction_ids:
+            analyzed_inst, = yield [("CALL_ARGS", [self.analyze, (inst,)])]
+            results.append(analyzed_inst)
+
+        raise primitive_functions.PrimitiveFinished(results)
+
+    def analyze_return(self, instruction):
+        """Tries to analyze the given 'return' instruction."""
+        def create_return(return_value):
+            return tree_ir.ReturnInstruction(
+                tree_ir.CompoundInstruction(
+                    return_value,
+                    tree_ir.DeleteEdgeInstruction(
+                        tree_ir.LoadLocalInstruction(jit_runtime.LOCALS_EDGE_NAME))))
+
+        if instruction.value is None:
+            raise primitive_functions.PrimitiveFinished(
+                create_return(
+                    tree_ir.EmptyInstruction()))
+        else:
+            retval, = yield [("CALL_ARGS", [self.analyze, (instruction.value,)])]
+            raise primitive_functions.PrimitiveFinished(
+                create_return(retval))
+
+    def analyze_if(self, instruction):
+        """Tries to analyze the given 'if' instruction."""
+        if instruction.else_clause is None:
+            (cond_r, true_r), = yield [
+                ("CALL_ARGS",
+                 [self.analyze_all,
+                  ([instruction.condition, instruction.if_clause],)])]
+            false_r = tree_ir.EmptyInstruction()
+        else:
+            (cond_r, true_r, false_r), = yield [
+                ("CALL_ARGS",
+                 [self.analyze_all,
+                  ([instruction.condition, instruction.if_clause, instruction.else_clause],)])]
+
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.SelectInstruction(
+                tree_ir.ReadValueInstruction(cond_r),
+                true_r,
+                false_r))
+
+    def analyze_while(self, instruction):
+        """Tries to analyze the given 'while' instruction."""
+        # Analyze the condition.
+        cond_r, = yield [("CALL_ARGS", [self.analyze, (instruction.condition,)])]
+        # Store the old enclosing loop on the stack, and make this loop the
+        # new enclosing loop.
+        old_loop_instruction = self.enclosing_loop_instruction
+        self.enclosing_loop_instruction = instruction
+        body_r, = yield [("CALL_ARGS", [self.analyze, (instruction.body,)])]
+        # Restore hte old enclosing loop.
+        self.enclosing_loop_instruction = old_loop_instruction
+        if self.jit.nop_insertion_enabled:
+            create_loop_body = lambda check, body: tree_ir.create_block(
+                check,
+                body_r,
+                tree_ir.NopInstruction())
+        else:
+            create_loop_body = tree_ir.CompoundInstruction
+
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.LoopInstruction(
+                create_loop_body(
+                    tree_ir.SelectInstruction(
+                        tree_ir.ReadValueInstruction(cond_r),
+                        tree_ir.EmptyInstruction(),
+                        tree_ir.BreakInstruction()),
+                    body_r)))
+
+    def analyze_constant(self, instruction):
+        """Tries to analyze the given 'constant' (literal) instruction."""
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.LiteralInstruction(instruction.constant_id))
+
+    def analyze_output(self, instruction):
+        """Tries to analyze the given 'output' instruction."""
+        value_val, = yield [("CALL_ARGS", [self.analyze, (instruction.value,)])]
+        raise primitive_functions.PrimitiveFinished(create_output(value_val))
+
+    def analyze_input(self, _):
+        """Tries to analyze the given 'input' instruction."""
+        raise primitive_functions.PrimitiveFinished(create_input(self.jit.input_function_enabled))
+
+    def analyze_resolve(self, instruction):
+        """Tries to analyze the given 'resolve' instruction."""
+        # To resolve a variable, we'll do something along the
+        # lines of:
+        #
+        #     if 'local_var' in locals():
+        #         tmp = local_var
+        #     else:
+        #         _globals, = yield [("RD", [task_root, "globals"])]
+        #         global_var, = yield [("RD", [_globals, var_name])]
+        #
+        #         if global_var is None:
+        #             raise Exception("Not found as global: %s" % (var_name))
+        #
+        #         tmp = global_var
+
+        name = self.local_name_map.get_local_name(instruction.variable.node_id)
+
+        if instruction.variable.name is None:
+            raise primitive_functions.PrimitiveFinished(
+                tree_ir.LoadLocalInstruction(name))
+
+        task_root = retrieve_task_root()
+        global_var = tree_ir.StoreLocalInstruction(
+            'global_var',
+            tree_ir.ReadDictionaryValueInstruction(
+                tree_ir.ReadDictionaryValueInstruction(
+                    task_root.create_load(),
+                    tree_ir.LiteralInstruction('globals')),
+                tree_ir.LiteralInstruction(instruction.variable.name)))
+
+        err_block = tree_ir.SelectInstruction(
+            tree_ir.BinaryInstruction(
+                global_var.create_load(),
+                'is',
+                tree_ir.LiteralInstruction(None)),
+            tree_ir.RaiseInstruction(
+                tree_ir.CallInstruction(
+                    tree_ir.LoadGlobalInstruction('Exception'),
+                    [tree_ir.LiteralInstruction(
+                        jit_runtime.GLOBAL_NOT_FOUND_MESSAGE_FORMAT % instruction.variable.name)
+                    ])),
+            tree_ir.EmptyInstruction())
+
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.SelectInstruction(
+                tree_ir.LocalExistsInstruction(name),
+                tree_ir.LoadLocalInstruction(name),
+                tree_ir.CompoundInstruction(
+                    tree_ir.create_block(
+                        task_root,
+                        global_var,
+                        err_block),
+                    global_var.create_load())))
+
+    def analyze_declare(self, instruction):
+        """Tries to analyze the given 'declare' function."""
+        self.register_local_var(instruction.variable.node_id)
+
+        name = self.local_name_map.get_local_name(instruction.variable.node_id)
+
+        # The following logic declares a local:
+        #
+        #     if 'local_name' not in locals():
+        #         local_name, = yield [("CN", [])]
+        #         yield [("CE", [LOCALS_NODE_NAME, local_name])]
+
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.SelectInstruction(
+                tree_ir.LocalExistsInstruction(name),
+                tree_ir.EmptyInstruction(),
+                tree_ir.create_new_local_node(
+                    name,
+                    tree_ir.LoadLocalInstruction(jit_runtime.LOCALS_NODE_NAME))))
+
+    def analyze_global(self, instruction):
+        """Tries to analyze the given 'global' (declaration) instruction."""
+        # To declare a variable, we'll do something along the
+        # lines of:
+        #
+        #     _globals, = yield [("RD", [task_root, "globals"])]
+        #     global_var = yield [("RD", [_globals, var_name])]
+        #
+        #     if global_var is None:
+        #         global_var, = yield [("CN", [])]
+        #         yield [("CD", [_globals, var_name, global_var])]
+        #
+        #     tmp = global_var
+
+        task_root = retrieve_task_root()
+        _globals = tree_ir.StoreLocalInstruction(
+            '_globals',
+            tree_ir.ReadDictionaryValueInstruction(
+                task_root.create_load(),
+                tree_ir.LiteralInstruction('globals')))
+
+        global_var = tree_ir.StoreLocalInstruction(
+            'global_var',
+            tree_ir.ReadDictionaryValueInstruction(
+                _globals.create_load(),
+                tree_ir.LiteralInstruction(instruction.variable.name)))
+
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.CompoundInstruction(
+                tree_ir.create_block(
+                    task_root,
+                    _globals,
+                    global_var,
+                    tree_ir.SelectInstruction(
+                        tree_ir.BinaryInstruction(
+                            global_var.create_load(),
+                            'is',
+                            tree_ir.LiteralInstruction(None)),
+                        tree_ir.create_block(
+                            global_var.create_store(
+                                tree_ir.CreateNodeInstruction()),
+                            tree_ir.CreateDictionaryEdgeInstruction(
+                                _globals.create_load(),
+                                tree_ir.LiteralInstruction(
+                                    instruction.variable.name),
+                                global_var.create_load())),
+                        tree_ir.EmptyInstruction())),
+                global_var.create_load()))
+
+    def analyze_assign(self, instruction):
+        """Tries to analyze the given 'assign' instruction."""
+        (var_r, value_r), = yield [
+            ("CALL_ARGS", [self.analyze_all, ([instruction.pointer, instruction.value],)])]
+
+        raise primitive_functions.PrimitiveFinished(create_assign(var_r, value_r))
+
+    def analyze_access(self, instruction):
+        """Tries to analyze the given 'access' instruction."""
+        var_r, = yield [("CALL_ARGS", [self.analyze, (instruction.pointer,)])]
+        raise primitive_functions.PrimitiveFinished(create_access(var_r))
+
+    def analyze_direct_call(self, callee_id, callee_name, argument_list):
+        """Tries to analyze a direct 'call' instruction."""
+        body_id, = yield [("RD", [callee_id, jit_runtime.FUNCTION_BODY_KEY])]
+
+        # Make this function dependent on the callee.
+        if body_id in self.jit.compilation_dependencies:
+            self.jit.compilation_dependencies[body_id].add(self.body_id)
+
+        # Figure out if the function might be an intrinsic.
+        intrinsic = self.jit.get_intrinsic(callee_name)
+
+        if intrinsic is None:
+            if callee_name is not None:
+                self.jit.register_global(body_id, callee_name)
+                compiled_func = self.jit.lookup_compiled_function(callee_name)
+            else:
+                compiled_func = None
+
+            if compiled_func is None:
+                # Compile the callee.
+                yield [
+                    ("CALL_ARGS", [self.jit.jit_compile, (self.task_root, body_id, callee_name)])]
+
+            # Get the callee's name.
+            compiled_func_name = self.jit.get_compiled_name(body_id)
+
+            # This handles the corner case where a constant node is called, like
+            # 'call(constant(9), ...)'. In this case, `callee_name` is `None`
+            # because 'constant(9)' doesn't give us a name. However, we can look up
+            # the name of the function at a specific node. If that turns out to be
+            # an intrinsic, then we still want to pick the intrinsic over a call.
+            intrinsic = self.jit.get_intrinsic(compiled_func_name)
+
+        # Analyze the argument dictionary.
+        named_args, = yield [("CALL_ARGS", [self.analyze_arguments, (argument_list,)])]
+
+        if intrinsic is not None:
+            raise primitive_functions.PrimitiveFinished(
+                apply_intrinsic(intrinsic, named_args))
+        else:
+            raise primitive_functions.PrimitiveFinished(
+                tree_ir.create_jit_call(
+                    tree_ir.LoadGlobalInstruction(compiled_func_name),
+                    named_args,
+                    tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME)))
+
+    def analyze_arguments(self, argument_list):
+        """Analyzes the given parameter-to-value mapping."""
+        named_args = []
+        for param_name, arg in argument_list:
+            param_val, = yield [("CALL_ARGS", [self.analyze, (arg,)])]
+            named_args.append((param_name, param_val))
+
+        raise primitive_functions.PrimitiveFinished(named_args)
+
+    def analyze_indirect_call(self, target, argument_list):
+        """Analyzes a call to an unknown function."""
+        # First off, let's analyze the callee and the argument list.
+        func_val, = yield [("CALL_ARGS", [self.analyze, (target,)])]
+        named_args, = yield [("CALL_ARGS", [self.analyze_arguments, (argument_list,)])]
+        func_val = tree_ir.StoreLocalInstruction(None, func_val)
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.create_block(
+                func_val,
+                create_indirect_call(func_val.create_load(), named_args)))
+
+    def try_analyze_direct_call(self, target, argument_list):
+        """Tries to analyze the given 'call' instruction as a direct call."""
+        if not self.jit.direct_calls_allowed:
+            raise jit_runtime.JitCompilationFailedException(
+                'Direct calls are not allowed by the JIT.')
+
+        # Figure out what the 'func' instruction's type is.
+        if isinstance(target, bytecode_ir.AccessInstruction):
+            # 'access(resolve(var))' instructions are translated to direct calls.
+            if isinstance(target.pointer, bytecode_ir.ResolveInstruction):
+                self.register_function_var(target.pointer.variable.node_id)
+                resolved_var_name = target.pointer.variable.name
+
+                if self.jit.thunks_enabled:
+                    # Analyze the argument dictionary.
+                    named_args, = yield [("CALL_ARGS", [self.analyze_arguments, (argument_list,)])]
+
+                    # Try to resolve the callee as an intrinsic.
+                    intrinsic = self.jit.get_intrinsic(resolved_var_name)
+                    if intrinsic is not None:
+                        raise primitive_functions.PrimitiveFinished(
+                            apply_intrinsic(intrinsic, named_args))
+
+                    # Otherwise, build a thunk.
+                    thunk_name = self.jit.jit_thunk_global(target.pointer.variable.name)
+                    raise primitive_functions.PrimitiveFinished(
+                        tree_ir.create_jit_call(
+                            tree_ir.LoadGlobalInstruction(thunk_name),
+                            named_args,
+                            tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME)))
+                else:
+                    # Try to look up the name as a global.
+                    _globals, = yield [("RD", [self.task_root, "globals"])]
+                    global_var, = yield [("RD", [_globals, resolved_var_name])]
+                    global_val, = yield [("RD", [global_var, "value"])]
+
+                    if global_val is not None:
+                        result, = yield [("CALL_ARGS", [self.analyze_direct_call, (
+                            global_val, resolved_var_name, argument_list)])]
+                        raise primitive_functions.PrimitiveFinished(result)
+        elif isinstance(target, bytecode_ir.ConstantInstruction):
+            # 'const(func_id)' instructions are also translated to direct calls.
+            result, = yield [("CALL_ARGS", [self.analyze_direct_call, (
+                target.constant_id, None, argument_list)])]
+            raise primitive_functions.PrimitiveFinished(result)
+
+        raise jit_runtime.JitCompilationFailedException(
+            "Cannot JIT function calls that target an unknown value as direct calls.")
+
+    def analyze_call(self, instruction):
+        """Tries to analyze the given 'call' instruction."""
+        def handle_exception(_):
+            # Looks like we'll have to compile it as an indirect call.
+            gen = self.analyze_indirect_call(instruction.target, instruction.argument_list)
+            result, = yield [("CALL", [gen])]
+            raise primitive_functions.PrimitiveFinished(result)
+
+        # Try to analyze the call as a direct call.
+        yield [("TRY", [])]
+        yield [("CATCH", [jit_runtime.JitCompilationFailedException, handle_exception])]
+        result, = yield [
+            ("CALL_ARGS",
+             [self.try_analyze_direct_call, (instruction.target, instruction.argument_list)])]
+        yield [("END_TRY", [])]
+        raise primitive_functions.PrimitiveFinished(result)
+
+    def analyze_break(self, instruction):
+        """Tries to analyze the given 'break' instruction."""
+        if instruction.loop == self.enclosing_loop_instruction:
+            raise primitive_functions.PrimitiveFinished(tree_ir.BreakInstruction())
+        else:
+            raise jit_runtime.JitCompilationFailedException(
+                "Multilevel 'break' is not supported by the baseline JIT.")
+
+    def analyze_continue(self, instruction):
+        """Tries to analyze the given 'continue' instruction."""
+        if instruction.loop == self.enclosing_loop_instruction:
+            raise primitive_functions.PrimitiveFinished(tree_ir.ContinueInstruction())
+        else:
+            raise jit_runtime.JitCompilationFailedException(
+                "Multilevel 'continue' is not supported by the baseline JIT.")
+
+    instruction_analyzers = {
+        bytecode_ir.SelectInstruction : analyze_if,
+        bytecode_ir.WhileInstruction : analyze_while,
+        bytecode_ir.ReturnInstruction : analyze_return,
+        bytecode_ir.ConstantInstruction : analyze_constant,
+        bytecode_ir.ResolveInstruction : analyze_resolve,
+        bytecode_ir.DeclareInstruction : analyze_declare,
+        bytecode_ir.GlobalInstruction : analyze_global,
+        bytecode_ir.AssignInstruction : analyze_assign,
+        bytecode_ir.AccessInstruction : analyze_access,
+        bytecode_ir.OutputInstruction : analyze_output,
+        bytecode_ir.InputInstruction : analyze_input,
+        bytecode_ir.CallInstruction : analyze_call,
+        bytecode_ir.BreakInstruction : analyze_break,
+        bytecode_ir.ContinueInstruction : analyze_continue
+    }

+ 96 - 0
kernel/modelverse_jit/cfg_data_structures.py

@@ -0,0 +1,96 @@
+"""Defines optimizations that replace Modelverse data structures by Python data structures."""
+
+import modelverse_jit.cfg_ir as cfg_ir
+
+SET_DEF_REWRITE_RULES = {
+    'dict_keys':
+    lambda def_def:
+    def_def.redefine(
+        cfg_ir.DirectFunctionCall(
+            cfg_ir.REVERSE_LIST_MACRO_NAME,
+            [('seq',
+              def_def.insert_before(
+                  cfg_ir.DirectFunctionCall(
+                      cfg_ir.READ_DICT_KEYS_MACRO_NAME,
+                      cfg_ir.get_def_value(def_def).argument_list,
+                      cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION,
+                      has_side_effects=False)))],
+            cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION,
+            has_side_effects=False))
+}
+
+def __redefine_as_list_len(use_def, def_def):
+    use_def.redefine(
+        cfg_ir.CreateNode(
+            use_def.insert_before(
+                cfg_ir.DirectFunctionCall(
+                    'len', [('seq', def_def)],
+                    cfg_ir.SIMPLE_POSITIONAL_CALLING_CONVENTION,
+                    has_side_effects=False))))
+
+SET_USE_REWRITE_RULES = {
+    ('set_pop', 'a'):
+    lambda use_def, def_def:
+    use_def.redefine(
+        cfg_ir.DirectFunctionCall(
+            'pop', [('self', def_def)],
+            cfg_ir.SELF_POSITIONAL_CALLING_CONVENTION)),
+    ('list_len', 'a'): __redefine_as_list_len,
+    ('read_nr_out', 'a'): __redefine_as_list_len
+}
+
+def get_call_def_rewriter(definition, def_rewrite_rules):
+    """Gets an appropriate rewrite rule from the given dictionary of call rewrite rules."""
+    if cfg_ir.is_call(definition, calling_convention=cfg_ir.JIT_CALLING_CONVENTION):
+        call = cfg_ir.get_def_value(definition)
+        if call.target_name in def_rewrite_rules:
+            return def_rewrite_rules[call.target_name]
+
+    return None
+
+def get_call_use_rewriter(use_definition, def_definition, use_rewrite_rules):
+    """Gets an appropriate rewrite rule from the given dictionary of call rewrite rules."""
+    if not cfg_ir.is_call(use_definition):
+        return None
+
+    call = cfg_ir.get_def_value(use_definition)
+    if call.calling_convention not in (
+            cfg_ir.JIT_CALLING_CONVENTION,
+            cfg_ir.JIT_NO_GC_CALLING_CONVENTION,
+            cfg_ir.JIT_CFG_INTRINSIC_CALLING_CONVENTION):
+        return None
+
+    for arg_name, arg_def in call.argument_list:
+        if arg_def == def_definition:
+            key = (call.target_name, arg_name)
+            if key in use_rewrite_rules:
+                return use_rewrite_rules[key]
+
+    return None
+
+def apply_rewrite_rules(
+        entry_point,
+        get_def_rewriter,
+        get_use_rewriter):
+    """Applies the given definition and use rewrite rules to all definitions and uses where a
+       rewrite rule can be found for both the definitions and the uses."""
+    # pylint: disable=I0011,W0108
+    cfg_ir.match_and_rewrite(
+        entry_point,
+        lambda def_def:
+        get_def_rewriter(def_def) is not None,
+        lambda use_def, def_def:
+        get_use_rewriter(use_def, def_def) is not None,
+        lambda def_def:
+        get_def_rewriter(def_def)(def_def),
+        lambda use_def, def_def:
+        get_use_rewriter(use_def, def_def)(use_def, def_def))
+
+def optimize_data_structures(entry_point):
+    """Optimizes data structures in the graph defined by the given entry point."""
+    apply_rewrite_rules(
+        entry_point,
+        lambda def_def:
+        get_call_def_rewriter(def_def, SET_DEF_REWRITE_RULES),
+        lambda use_def, def_def:
+        get_call_use_rewriter(use_def, def_def, SET_USE_REWRITE_RULES))

+ 113 - 0
kernel/modelverse_jit/cfg_dominators.py

@@ -0,0 +1,113 @@
+"""Computes dominator trees for control-flow graphs."""
+
+from collections import defaultdict
+import modelverse_jit.cfg_ir as cfg_ir
+
+def sort_postorder(entry_point):
+    """Produces a postorder traversal of the graph with the given block as entry point."""
+    processed = set()
+    results = []
+    def __sort_postorder_step(block):
+        if block in processed:
+            return
+
+        processed.add(block)
+        for branch in block.flow.branches():
+            __sort_postorder_step(branch.block)
+
+        results.append(block)
+
+    __sort_postorder_step(entry_point)
+    return results
+
+class DominatorTree(object):
+    """A data structure that represents a dominator tree."""
+    def __init__(self, immediate_dominators):
+        self.immediate_dominators = immediate_dominators
+        self.dominator_sets = {}
+
+    def get_immediate_dominator(self, block):
+        """Gets the given block's immediate dominator."""
+        return self.immediate_dominators[block]
+
+    def get_dominators(self, block):
+        """Gets the given block's set of all dominators."""
+        if block in self.dominator_sets:
+            return self.dominator_sets
+        else:
+            idom = self.get_immediate_dominator(block)
+            if idom == block:
+                results = set([block])
+            else:
+                results = set(self.get_dominators(idom))
+                results.add(block)
+
+            self.dominator_sets[block] = results
+            return results
+
+    def dominates_block(self, dominator, dominated):
+        """Tests if the first block dominates the second."""
+        return dominator in self.get_dominators(dominated)
+
+    def dominates_instruction(self, dominator, dominated):
+        """Tests if the first instruction dominates the second."""
+        dominator_block = dominator.block
+        dominated_block = dominated.block
+        if dominator_block == dominated_block:
+            return dominator.definition_index < dominated.definition_index
+        else:
+            return self.dominates_block(dominator_block, dominated_block)
+
+# The algorithms below are based on "A Simple, Fast Dominance Algorithm" by
+# Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy
+# (http://www.cs.rice.edu/~keith/Embed/dom.pdf)
+
+def intersect_immediate_dominators(block1, block2, idoms, postorder_nums):
+    """Computes the intersection of the given blocks' immediate dominators."""
+    finger1 = block1
+    finger2 = block2
+    while finger1 != finger2:
+        while postorder_nums[finger1] < postorder_nums[finger2]:
+            finger1 = idoms[finger1]
+
+        while postorder_nums[finger2] < postorder_nums[finger1]:
+            finger2 = idoms[finger2]
+    return finger1
+
+def get_immediate_dominators(entry_point):
+    """Computes the immediate dominators of the control-flow graph defined by the given entry
+       point."""
+    predecessor_map = cfg_ir.get_all_predecessor_blocks(entry_point)
+    idoms = {}
+    postorder_sort = sort_postorder(entry_point)
+    postorder_nums = {}
+    for i, elem in enumerate(postorder_sort):
+        postorder_nums[elem] = i
+        idoms[elem] = None
+
+    idoms[entry_point] = entry_point
+
+    changed = True
+    while changed:
+        changed = False
+        for block in reversed(postorder_sort):
+            if block == entry_point:
+                continue
+
+            new_idom = None
+            for pred in predecessor_map[block]:
+                assert pred in postorder_nums
+                if new_idom is None:
+                    new_idom = pred
+                elif idoms[pred] is not None:
+                    new_idom = intersect_immediate_dominators(pred, new_idom, idoms, postorder_nums)
+
+            if idoms[block] is not new_idom:
+                idoms[block] = new_idom
+                changed = True
+
+    return idoms
+
+def get_dominator_tree(entry_point):
+    """Constructs the dominator tree for the control-flow graph defined by the given entry point."""
+    return DominatorTree(get_immediate_dominators(entry_point))

File diff suppressed because it is too large
+ 1209 - 0
kernel/modelverse_jit/cfg_ir.py


+ 542 - 0
kernel/modelverse_jit/cfg_optimization.py

@@ -0,0 +1,542 @@
+"""Optimizes and analyzes CFG-IR."""
+
+from collections import defaultdict
+import modelverse_jit.cfg_ir as cfg_ir
+import modelverse_jit.cfg_dominators as cfg_dominators
+import modelverse_jit.cfg_ssa_construction as cfg_ssa_construction
+import modelverse_jit.cfg_data_structures as cfg_data_structures
+import modelverse_kernel.primitives as primitive_functions
+
+def is_empty_block(block):
+    """Tests if the given block contains no parameters or definitions."""
+    return len(block.parameters) == 0 and len(block.definitions) == 0
+
+def optimize_flow(block):
+    """Optimizes the given block's flow instruction."""
+    changed = True
+    while changed:
+        changed = False
+
+        # Select flow with a literal condition can be optimized to a direct jump.
+        if (isinstance(block.flow, cfg_ir.SelectFlow)
+                and cfg_ir.is_literal_def(block.flow.condition)):
+            literal = cfg_ir.get_literal_def_value(block.flow.condition)
+            block.flow = cfg_ir.JumpFlow(
+                block.flow.if_branch if literal else block.flow.else_branch)
+            changed = True
+
+        # Jumps to blocks which contain no parameters or definitions can be replaced
+        # by the target block's flow.
+        if (isinstance(block.flow, cfg_ir.JumpFlow)
+                and is_empty_block(block.flow.branch.block)
+                and block.flow.branch.block is not block):
+            block.flow = block.flow.branch.block.flow
+            changed = True
+
+        # Branches to blocks which contain nothing but a jump can be replaced by branches
+        # to the jump's target.
+        for branch in block.flow.branches():
+            if (is_empty_block(branch.block)
+                    and branch.block is not block
+                    and isinstance(branch.block.flow, cfg_ir.JumpFlow)):
+                new_branch = branch.block.flow.branch
+                branch.block = new_branch.block
+                branch.arguments = new_branch.arguments
+                changed = True
+
+def optimize_graph_flow(entry_point):
+    """Optimizes all flow instructions in the graph defined by the given entry point."""
+    for block in cfg_ir.get_all_blocks(entry_point):
+        optimize_flow(block)
+
+def merge_blocks(entry_point):
+    """Merges blocks which have exactly one predecessor with said predecessor, if the
+       predecessor has a jump flow instruction."""
+    predecessor_map = cfg_ir.get_all_predecessor_blocks(entry_point)
+    queue = set(predecessor_map.keys())
+    queue.add(entry_point)
+    def __do_merge(source, target):
+        target_params = list(target.parameters)
+        branch_args = list(source.flow.branch.arguments)
+        for target_param, branch_arg in zip(target_params, branch_args):
+            target.remove_parameter(target_param)
+            target_param.redefine(branch_arg)
+            source.append_definition(target_param)
+
+        target_defs = list(target.definitions)
+        for target_def in target_defs:
+            target.remove_definition(target_def)
+            source.append_definition(target_def)
+
+        source.flow = target.flow
+        for preds in predecessor_map.values():
+            if target in preds:
+                preds[preds.index(target)] = source
+                # preds.remove(target)
+                # preds.add(source)
+
+    while len(queue) > 0:
+        block = queue.pop()
+        if isinstance(block.flow, cfg_ir.JumpFlow):
+            next_block = block.flow.branch.block
+            preds = predecessor_map[next_block]
+            if (len(preds) == 1
+                    and next(iter(preds)) == block
+                    and block != next_block
+                    and next_block != entry_point):
+                __do_merge(block, next_block)
+                del predecessor_map[next_block]
+                queue.add(block)
+                if next_block in queue:
+                    queue.remove(next_block)
+
+def elide_local_checks(entry_point):
+    """Tries to elide redundant checks on local variables."""
+    # The plan here is to replace all check-local-exists defs by literals if
+    # they are either dominated by an appropriate declare-local or not reachable
+    # from a declare-local.
+    local_checks = []
+    local_defs = defaultdict(list)
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for definition in block.definitions:
+            def_value = definition.value
+            if isinstance(def_value, cfg_ir.CheckLocalExists):
+                local_checks.append((def_value.variable.node_id, definition))
+            elif isinstance(def_value, cfg_ir.DeclareLocal):
+                local_defs[def_value.variable.node_id].append(definition)
+
+    dominator_tree = cfg_dominators.get_dominator_tree(entry_point)
+    reachable_blocks = cfg_ir.get_all_reachable_blocks(entry_point)
+    for (variable, check) in local_checks:
+        is_reachable = False
+        for local_def in local_defs[variable]:
+            if dominator_tree.dominates_instruction(local_def, check):
+                # Check is dominated by a definition. Replace it by a 'True' literal.
+                check.redefine(cfg_ir.Literal(True))
+                is_reachable = True
+                break
+            elif check.block in reachable_blocks[local_def.block]:
+                is_reachable = True
+
+        if not is_reachable:
+            # Check cannot be reached from any definition. Replace it by a 'False' literal.
+            check.redefine(cfg_ir.Literal(False))
+
+def eliminate_unused_definitions(entry_point):
+    """Tries to eliminate unused definitions in the control-flow graphb defined by the
+       given entry point."""
+    def_dependencies = defaultdict(set)
+    root_defs = set()
+    # Gather dependencies.
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for definition in block.parameters + block.definitions:
+            all_dependencies = list(definition.get_all_dependencies())
+            def_dependencies[definition].update(
+                [dep for dep in all_dependencies
+                 if isinstance(dep, cfg_ir.Definition)])
+
+            if len(all_dependencies) > 0 and definition.has_bidirectional_dependencies():
+                for dep in all_dependencies:
+                    def_dependencies[dep].add(definition)
+
+            if definition.has_side_effects():
+                root_defs.add(definition)
+
+        for dep in block.flow.get_dependencies():
+            if isinstance(dep, cfg_ir.Definition):
+                root_defs.add(dep)
+            else:
+                assert isinstance(dep, cfg_ir.Branch)
+                for param, arg in zip(dep.block.parameters, dep.arguments):
+                    def_dependencies[param].add(arg)
+
+    # Figure out which definitions are live.
+    live_defs = set()
+    def __mark_live(definition):
+        if definition in live_defs:
+            return
+
+        live_defs.add(definition)
+        if definition in def_dependencies:
+            for dep in def_dependencies[definition]:
+                __mark_live(dep)
+
+    for root in root_defs:
+        __mark_live(root)
+
+    # Remove all dead definitions.
+    dead_defs = set.difference(set(def_dependencies.keys()), live_defs)
+    dead_phis = set()
+    for dead_def in dead_defs:
+        if isinstance(dead_def.value, cfg_ir.BlockParameter):
+            dead_phis.add(dead_def)
+        else:
+            dead_def.block.remove_definition(dead_def)
+
+    erase_parameters(entry_point, dead_phis)
+
+def eliminate_trivial_phis(entry_point):
+    """Eliminates trivial block parameters, i.e., block parameters which are really
+       aliases."""
+    phi_values = defaultdict(set)
+    all_blocks = list(cfg_ir.get_all_blocks(entry_point))
+    for block in all_blocks:
+        for branch in block.flow.branches():
+            for phi, arg in zip(branch.block.parameters, branch.arguments):
+                phi_values[phi].add(arg)
+
+    replacements = []
+    for block in all_blocks:
+        block_parameters = list(block.parameters)
+        for parameter_def in block_parameters:
+            trivial_phi_val = cfg_ir.get_trivial_phi_value(
+                parameter_def, phi_values[parameter_def])
+            if trivial_phi_val is not None:
+                replacements.append((parameter_def, trivial_phi_val))
+
+    erase_parameters(entry_point, set([parameter_def for parameter_def, _ in replacements]))
+
+    for parameter_def, trivial_phi_val in replacements:
+        block = parameter_def.block
+        parameter_def.redefine(trivial_phi_val)
+        block.prepend_definition(parameter_def)
+
+def erase_parameters(entry_point, parameters_to_erase):
+    """Erases all arguments for the given set of parameters, and then takes out the
+       parameters themselves."""
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for branch in block.flow.branches():
+            new_arg_list = []
+            for parameter, arg in zip(branch.block.parameters, branch.arguments):
+                if parameter not in parameters_to_erase:
+                    new_arg_list.append(arg)
+            branch.arguments = new_arg_list
+
+    for parameter_def in parameters_to_erase:
+        parameter_def.block.remove_parameter(parameter_def)
+
+def apply_cfg_intrinsic(intrinsic_function, original_definition, named_args):
+    """Applies the given intrinsic to the given sequence of named arguments."""
+    kwargs = dict(named_args)
+    kwargs['original_def'] = original_definition
+    return intrinsic_function(**kwargs)
+
+def try_redefine_as_direct_call(definition, jit, called_globals):
+    """Tries to redefine the given indirect call definition as a direct call."""
+    call = cfg_ir.get_def_value(definition)
+    if not isinstance(call, cfg_ir.IndirectFunctionCall):
+        return
+
+    target = cfg_ir.get_def_value(call.target)
+    if isinstance(target, cfg_ir.LoadPointer):
+        loaded_ptr = cfg_ir.get_def_value(target.pointer)
+        if isinstance(loaded_ptr, cfg_ir.ResolveGlobal):
+            resolved_var_name = loaded_ptr.variable.name
+            called_globals.add(loaded_ptr)
+
+            # Try to resolve the callee as an intrinsic.
+            intrinsic = jit.get_cfg_intrinsic(resolved_var_name)
+            if intrinsic is not None:
+                definition.redefine(
+                    cfg_ir.DirectFunctionCall(
+                        resolved_var_name, call.argument_list,
+                        cfg_ir.JIT_CFG_INTRINSIC_CALLING_CONVENTION))
+            else:
+                # Otherwise, build a thunk.
+                thunk_name = jit.jit_thunk_global(resolved_var_name)
+                calling_convention = (
+                    cfg_ir.JIT_NO_GC_CALLING_CONVENTION
+                    if jit.get_intrinsic(thunk_name) is not None
+                    else cfg_ir.JIT_CALLING_CONVENTION)
+                definition.redefine(
+                    cfg_ir.DirectFunctionCall(
+                        thunk_name, call.argument_list, calling_convention))
+                called_globals.add(loaded_ptr)
+    elif isinstance(target, cfg_ir.Literal):
+        node_id = target.literal
+        thunk_name = jit.jit_thunk_constant_function(node_id)
+        definition.redefine(
+            cfg_ir.DirectFunctionCall(
+                thunk_name, call.argument_list, cfg_ir.JIT_CALLING_CONVENTION))
+
+def get_checked_global(definition):
+    """If the definition is a check that tests if a global does not exist, then
+       the instruction that resolves the global is returned; otherwise None."""
+    def_value = cfg_ir.get_def_value(definition)
+    if not isinstance(def_value, cfg_ir.Binary):
+        return None
+
+    if def_value.operator != 'is':
+        return None
+
+    def __get_checked_global_single_dir(lhs, rhs):
+        if (isinstance(lhs, cfg_ir.ResolveGlobal)
+                and isinstance(rhs, cfg_ir.Literal)
+                and rhs.literal is None):
+            return lhs
+        else:
+            return None
+
+    bin_lhs = cfg_ir.get_def_value(def_value.lhs)
+    bin_rhs = cfg_ir.get_def_value(def_value.rhs)
+    result = __get_checked_global_single_dir(bin_lhs, bin_rhs)
+    if result is None:
+        result = __get_checked_global_single_dir(bin_rhs, bin_lhs)
+
+    return result
+
+def optimize_calls(entry_point, jit):
+    """Converts indirect calls to direct calls in the control-flow graph defined by the
+       given entry point."""
+    called_globals = set()
+    global_exists_defs = defaultdict(list)
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for definition in block.definitions:
+            checked_global = get_checked_global(definition)
+            if checked_global is not None:
+                global_exists_defs[checked_global].append(definition)
+            else:
+                try_redefine_as_direct_call(definition, jit, called_globals)
+
+    for resolve_global in called_globals:
+        for exists_def in global_exists_defs[resolve_global]:
+            exists_def.redefine(cfg_ir.Literal(False))
+
+def expand_cfg_intrinsics(entry_point, jit):
+    """Expands CFG JIT intrinsics in the control-flow graph defined by the given entry point."""
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for definition in block.definitions:
+            def_value = definition.value
+            if (isinstance(def_value, cfg_ir.DirectFunctionCall)
+                    and def_value.calling_convention ==
+                    cfg_ir.JIT_CFG_INTRINSIC_CALLING_CONVENTION):
+                intrinsic = jit.get_cfg_intrinsic(def_value.target_name)
+                apply_cfg_intrinsic(intrinsic, definition, def_value.argument_list)
+
+def simplify_values(entry_point):
+    """Simplifies values in the control-flow graph defined by the given entry point."""
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for definition in block.definitions:
+            def_val = cfg_ir.get_def_value(definition)
+            if isinstance(def_val, cfg_ir.Read):
+                read_node = cfg_ir.get_def_value(def_val.node)
+                if isinstance(read_node, cfg_ir.CreateNode):
+                    definition.redefine(read_node.value)
+            elif isinstance(def_val, cfg_ir.Binary):
+                lhs = cfg_ir.get_def_value(def_val.lhs)
+                rhs = cfg_ir.get_def_value(def_val.rhs)
+                if isinstance(lhs, cfg_ir.Literal) and isinstance(rhs, cfg_ir.Literal):
+                    definition.redefine(
+                        cfg_ir.Literal(
+                            eval('%r %s %r' % (lhs.literal, def_val.operator, rhs.literal))))
+            elif isinstance(def_val, cfg_ir.Unary):
+                operand = cfg_ir.get_def_value(def_val.operand)
+                if isinstance(operand, cfg_ir.Literal):
+                    definition.redefine(
+                        cfg_ir.Literal(
+                            eval('%s %r' % (def_val.operator, operand.literal))))
+
+def inline_constants(entry_point):
+    """Replaces reads of constant nodes by the literals they contain."""
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for definition in block.definitions:
+            def_val = cfg_ir.get_def_value(definition)
+            if isinstance(def_val, cfg_ir.Read):
+                read_node = cfg_ir.get_def_value(def_val.node)
+                if isinstance(read_node, cfg_ir.Literal):
+                    val, = yield [("RV", [read_node.literal])]
+                    definition.redefine(cfg_ir.Literal(val))
+
+def expand_indirect_definitions(entry_point):
+    """Replaces indirect definitions by the values referred to by those definitions."""
+    def __expand_indirect_defs(value):
+        dependencies = value.get_dependencies()
+        if len(dependencies) == 0:
+            return value
+        else:
+            new_dependencies = []
+            for dep in dependencies:
+                new_dep = dep
+                if isinstance(new_dep, cfg_ir.Definition):
+                    while isinstance(new_dep.value, cfg_ir.Definition):
+                        new_dep = new_dep.value
+                else:
+                    new_dep = __expand_indirect_defs(new_dep)
+
+                new_dependencies.append(new_dep)
+            return value.create(new_dependencies)
+
+    for block in cfg_ir.get_all_blocks(entry_point):
+        block_definitions = list(block.definitions)
+        for definition in block_definitions:
+            if isinstance(definition.value, cfg_ir.Definition):
+                block.remove_definition(definition)
+            else:
+                definition.redefine(
+                    __expand_indirect_defs(definition.value))
+
+        block.flow = __expand_indirect_defs(block.flow)
+
+def optimize_reads(entry_point):
+    """Tries to replace repeated reads by a single read."""
+    cfg_ir.match_and_rewrite(
+        entry_point,
+        lambda _: True,
+        lambda use_def, _: cfg_ir.is_value_def(use_def, cfg_ir.Read),
+        lambda def_def:
+        def_def.redefine(
+            cfg_ir.Read(def_def.insert_before(def_def.value))),
+        lambda use_def, def_def: use_def.redefine(def_def))
+
+def protect_from_gc(entry_point):
+    """Protects locals in the control-flow graph defined by the given
+       entry point from the GC."""
+    root_node = entry_point.prepend_definition(cfg_ir.AllocateRootNode())
+    def protect_def_from_gc(definition):
+        """Protects the given definition from the GC."""
+        definition.insert_after(cfg_ir.create_gc_protect(definition, root_node))
+
+    def maybe_protect_def_from_gc(definition):
+        """Protects the given definition from the GC, if its result is not None."""
+        definition.insert_after(cfg_ir.create_conditional_gc_protect(definition, root_node))
+
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for definition in block.definitions:
+            def_value = cfg_ir.get_def_value(definition)
+            if isinstance(def_value, cfg_ir.CreateNode):
+                protect_def_from_gc(definition)
+            elif (isinstance(def_value, cfg_ir.IndirectFunctionCall)
+                  or (isinstance(def_value, cfg_ir.DirectFunctionCall)
+                      and (def_value.calling_convention == cfg_ir.JIT_CALLING_CONVENTION
+                           or def_value.calling_convention == cfg_ir.JIT_NO_GC_CALLING_CONVENTION
+                           or def_value.calling_convention == cfg_ir.MACRO_IO_CALLING_CONVENTION)
+                      and def_value.has_value())):
+                maybe_protect_def_from_gc(definition)
+
+        if isinstance(block.flow, (cfg_ir.ReturnFlow, cfg_ir.ThrowFlow)):
+            block.append_definition(cfg_ir.DeallocateRootNode(root_node))
+
+def elide_gc_protects(entry_point):
+    """Tries to elide GC protection values."""
+    # We don't need to protect a value from the GC if it is used for the
+    # last time _before_ the GC has an opportunity to kick in. To simplify
+    # things, we'll do a quick block-based analysis.
+    def __may_cause_gc(definition):
+        def_value = cfg_ir.get_def_value(definition)
+        if isinstance(def_value, cfg_ir.IndirectFunctionCall):
+            return True
+        elif (isinstance(def_value, cfg_ir.DirectFunctionCall)
+              and (def_value.calling_convention == cfg_ir.JIT_CALLING_CONVENTION
+                   or def_value.calling_convention == cfg_ir.MACRO_IO_CALLING_CONVENTION)):
+            return True
+        else:
+            return False
+
+    def __get_protected_def(def_or_value):
+        value = cfg_ir.get_def_value(def_or_value)
+        if cfg_ir.is_call(
+                value, target_name=cfg_ir.GC_PROTECT_MACRO_NAME,
+                calling_convention=cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION):
+            _, protected_def = value.argument_list[0]
+            return protected_def
+        elif cfg_ir.is_call(
+                value, target_name=cfg_ir.MAYBE_GC_PROTECT_MACRO_NAME,
+                calling_convention=cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION):
+            _, protected_def = value.argument_list[1]
+            return protected_def
+        else:
+            return None
+
+    def_blocks = {}
+    def __register_def_or_use(definition, block):
+        if definition in def_blocks and def_blocks[definition] != block:
+            # Definition seems to be used across basic blocks.
+            ineligible_defs.add(definition)
+
+        def_blocks[definition] = block
+
+    ineligible_defs = set()
+    def_protections = defaultdict(list)
+    for block in cfg_ir.get_all_blocks(entry_point):
+        no_gc_defs = set()
+        block_defs = set()
+        first_gc = {}
+        last_def_uses = {}
+        for i, definition in enumerate(block.definitions):
+            if isinstance(definition.value, cfg_ir.Definition):
+                # Handling definitions of definitions is complicated and they should already have
+                # been expanded at this point. Just mark them as ineligible.
+                ineligible_defs.add(definition)
+                ineligible_defs.add(definition.value)
+                continue
+
+            protected_def = __get_protected_def(definition)
+            if protected_def is not None:
+                # We just ran into a gc_protect/maybe_gc_protect.
+                def_protections[protected_def].append(definition)
+                continue
+
+            block_defs.add(definition)
+            __register_def_or_use(definition, block)
+
+            for dependency in definition.get_all_dependencies():
+                __register_def_or_use(dependency, block)
+                last_def_uses[dependency] = i
+
+            if __may_cause_gc(definition):
+                for gc_def in no_gc_defs:
+                    first_gc[gc_def] = i
+                no_gc_defs = set()
+
+            no_gc_defs.add(definition)
+
+        # Mark all branch arguments as ineligible.
+        for branch in block.flow.branches():
+            ineligible_defs.update(branch.arguments)
+
+        for dependency in block.flow.get_dependencies():
+            last_def_uses[dependency] = None
+
+        for definition in block_defs:
+            if definition in ineligible_defs:
+                # Definition was already ineligible.
+                continue
+
+            # Mark `definition` as ineligible if there is a GC definition in the range of
+            # definitions (definition, last_def_uses[definition]].
+            if definition in first_gc:
+                if definition in last_def_uses:
+                    last_use = last_def_uses[definition]
+                    if last_use is None or first_gc[definition] <= last_use:
+                        ineligible_defs.add(definition)
+
+    # Elide all GC protections for definitions which are not in the `ineligible_defs` set.
+    for protected, protections in def_protections.items():
+        if protected not in ineligible_defs:
+            for protect_def in protections:
+                protect_def.redefine(cfg_ir.Literal(None))
+
+def optimize(entry_point, jit):
+    """Optimizes the control-flow graph defined by the given entry point.
+       A potentially altered entry point is returned."""
+    optimize_graph_flow(entry_point)
+    elide_local_checks(entry_point)
+    optimize_graph_flow(entry_point)
+    eliminate_trivial_phis(entry_point)
+    entry_point = cfg_ssa_construction.construct_ssa_form(entry_point)
+    if jit.direct_calls_allowed:
+        optimize_calls(entry_point, jit)
+    cfg_data_structures.optimize_data_structures(entry_point)
+    expand_cfg_intrinsics(entry_point, jit)
+    yield [("CALL_ARGS", [inline_constants, (entry_point,)])]
+    optimize_reads(entry_point)
+    simplify_values(entry_point)
+    eliminate_unused_definitions(entry_point)
+    optimize_graph_flow(entry_point)
+    expand_indirect_definitions(entry_point)
+    eliminate_unused_definitions(entry_point)
+    merge_blocks(entry_point)
+    protect_from_gc(entry_point)
+    elide_gc_protects(entry_point)
+    eliminate_unused_definitions(entry_point)
+    raise primitive_functions.PrimitiveFinished(entry_point)

+ 268 - 0
kernel/modelverse_jit/cfg_ssa_construction.py

@@ -0,0 +1,268 @@
+"""Converts 'declare-local', 'load' and 'store' instructions into SSA form."""
+
+from collections import defaultdict
+import modelverse_jit.cfg_ir as cfg_ir
+
+def get_local_id(def_or_value):
+    """Gets the node of the local resolved or declared by the given definition or value.
+       If the given definition or value does not refer to a 'resolve-local' or
+       'declare-local' node, then None is returned."""
+    value = cfg_ir.get_def_value(def_or_value)
+    if isinstance(value, (cfg_ir.ResolveLocal, cfg_ir.DeclareLocal)):
+        return value.variable.node_id
+    else:
+        return None
+
+def get_ineligible_local_ids(entry_point):
+    """Finds the ids of all local variables which are not eligible for conversion to SSA form."""
+    # Local variables are eligible for conversion to SSA form if their pointer node is never
+    # leaked to the outside world. So we know that we can safely convert a local to SSA form
+    # if 'resolve-local' values are only used by 'load' and 'store' values.
+    ineligible_local_ids = set()
+    def __maybe_mark_ineligible(def_or_value):
+        local_id = get_local_id(def_or_value)
+        if local_id is not None:
+            ineligible_local_ids.add(local_id)
+
+    for block in cfg_ir.get_all_blocks(entry_point):
+        for definition in block.definitions + [block.flow]:
+            value = cfg_ir.get_def_value(definition)
+            if isinstance(value, cfg_ir.LoadPointer):
+                # Loading a pointer to a local is fine.
+                pass
+            elif isinstance(value, cfg_ir.StoreAtPointer):
+                # Storing a value in a local is fine, too.
+                # But be careful not to ignore a store where the stored value is a local pointer.
+                __maybe_mark_ineligible(value.value)
+            else:
+                # Walk over the dependencies, and mark them all as ineligible for
+                # local-to-SSA conversion.
+                for dependency in value.get_all_dependencies():
+                    __maybe_mark_ineligible(dependency)
+
+    return ineligible_local_ids
+
+def construct_ssa_form(entry_point):
+    """Converts local variables into SSA form in the graph defined by the given entry point.
+       A potentially new entry block is returned."""
+    # Build some helper data structures.
+    all_blocks = list(cfg_ir.get_all_blocks(entry_point))
+    ineligible_locals = get_ineligible_local_ids(entry_point)
+    predecessor_map = cfg_ir.get_all_predecessor_blocks(entry_point)
+
+    # Create the SSA construction state.
+    state = SSAConstructionState(all_blocks, ineligible_locals, predecessor_map)
+
+    # Fill all blocks in the graph.
+    for block in all_blocks:
+        state.fill_block(block)
+
+    # Update branches.
+    for block in all_blocks:
+        state.update_block_branches(block)
+
+    # Nullify entry point parameters.
+    return nullify_entry_block_parameters(entry_point)
+
+def nullify_entry_block_parameters(entry_point):
+    """Creates or returns an entry block that takes no parameters.
+       The (new) entry block is returned."""
+    # The SSA construction algorithm has the nasty habit of replacing potentially
+    # undefined variables by entry block parameters. Codegen doesn't like that one
+    # bit: it assumes that all block parameters are defined by the branches to those
+    # parameters -- but there usually is no branch to the entry point!
+    #
+    # We can fix this by either replacing the block-parameters in the entry block by
+    # definitions, or by creating a new entry point that jumps to the old entry
+    # point with an argument list. The former construction is more efficient, but it is only
+    # correct if there are no pre-existing branches to the entry point.
+    #
+    # We could build the predecessor map block and use the first option if possible,
+    # but we won't; trivial phi elimination followed by block merging will reduce
+    # the second construction to the first one anyway.
+    if len(entry_point.parameters) == 0:
+        return entry_point
+
+    pre_entry_point = cfg_ir.BasicBlock(entry_point.counter)
+    arg_list = []
+    for _ in entry_point.parameters:
+        literal_def = pre_entry_point.append_definition(cfg_ir.Literal(None))
+        arg_list.append(literal_def)
+
+    pre_entry_point.flow = cfg_ir.create_jump(entry_point, arg_list)
+    return pre_entry_point
+
+# The algorithms below are based on
+# Simple and Efficient Construction of Static Single Assignment Form by M. Braun et al
+# (https://pp.info.uni-karlsruhe.de/uploads/publikationen/braun13cc.pdf).
+
+class SSAConstructionState(object):
+    """Encapsulates state related to SSA construction."""
+    def __init__(self, all_blocks, ineligible_locals, predecessor_map):
+        self.all_blocks = all_blocks
+        self.ineligible_locals = ineligible_locals
+        self.predecessor_map = predecessor_map
+        # `current_defs` is a local node id -> basic block -> definition map.
+        self.current_defs = defaultdict(dict)
+        # `incomplete_phis` is a basic block -> local node id -> block parameter def map.
+        self.incomplete_phis = defaultdict(dict)
+        # `extra_phi_operands` is a basic block -> block parameter def -> def map.
+        self.extra_phi_operands = defaultdict(dict)
+        self.processed_blocks = set()
+        self.filled_blocks = set()
+        self.sealed_blocks = set()
+
+    def read_variable(self, block, node_id):
+        """Reads the latest definition of the local variable with the
+           given node id for the specified block."""
+        if block in self.current_defs[node_id]:
+            return self.current_defs[node_id][block]
+        else:
+            return self.read_variable_recursive(block, node_id)
+
+    def write_variable(self, block, node_id, value):
+        """Writes the given value to the local with the specified id in the
+           specified block."""
+        self.current_defs[node_id][block] = value
+
+    def read_variable_recursive(self, block, node_id):
+        """Reads the latest definition of the local variable with the
+           given node id from one of the given block's predecessor blocks."""
+        if block not in self.sealed_blocks:
+            # Create an incomplete phi.
+            val = block.append_parameter(cfg_ir.BlockParameter())
+            self.incomplete_phis[block][node_id] = val
+        elif len(self.predecessor_map[block]) == 1:
+            # Optimize the common case of one predecessor: no phi needed.
+            pred = next(iter(self.predecessor_map[block]))
+            val = self.read_variable(pred, node_id)
+        else:
+            # Break potential cycles with an operandless phi.
+            val = block.append_parameter(cfg_ir.BlockParameter())
+            self.write_variable(block, node_id, val)
+            val = self.add_phi_operands(node_id, val)
+
+        self.write_variable(block, node_id, val)
+        return val
+
+    def add_phi_operands(self, node_id, phi_def):
+        """Finds out which arguments branches should provide for the given block
+           parameter definition."""
+        # Determine operands from predecessors
+        all_values = []
+        for pred in self.predecessor_map[phi_def.block]:
+            arg = self.read_variable(pred, node_id)
+            self.extra_phi_operands[pred][phi_def] = arg
+            all_values.append(arg)
+        return self.try_remove_trivial_phi(phi_def, all_values)
+
+    def try_remove_trivial_phi(self, phi_def, values):
+        """Tries to remove a trivial block parameter definition."""
+        # This is a somewhat simplified (and less powerful) version of the
+        # algorithm in the SSA construction paper. That's kind of okay, though;
+        # trivial phi elimination is also implemented as a separate pass in the
+        # optimization pipeline.
+        trivial_phi_val = cfg_ir.get_trivial_phi_value(phi_def, values)
+        if trivial_phi_val is None:
+            return phi_def
+        else:
+            for pred in self.predecessor_map[phi_def.block]:
+                del self.extra_phi_operands[pred][phi_def]
+
+            phi_def.block.remove_parameter(phi_def)
+            phi_def.redefine(trivial_phi_val)
+            phi_def.block.prepend_definition(phi_def)
+            return trivial_phi_val
+
+    def has_sealed(self, block):
+        """Tells if the given block has been sealed yet."""
+        return block in self.sealed_blocks
+
+    def can_seal(self, block):
+        """Tells if the given block can be sealed right away."""
+        # A block can be sealed if all if its predecessors have been filled.
+        return all(
+            [predecessor in self.filled_blocks for predecessor in self.predecessor_map[block]])
+
+    def seal_all_sealable_blocks(self):
+        """Seals all sealable blocks."""
+        for block in self.all_blocks:
+            if self.can_seal(block):
+                self.seal_block(block)
+
+    def seal_block(self, block):
+        """Seals the given block."""
+        if self.has_sealed(block):
+            return
+
+        for node_id, phi_def in self.incomplete_phis[block].items():
+            self.add_phi_operands(node_id, phi_def)
+
+        self.sealed_blocks.add(block)
+
+    def has_filled(self, block):
+        """Tells if the given block has been filled yet."""
+        return block in self.filled_blocks
+
+    def fill_block(self, block):
+        """Visits all definitions in the given block. Locals are converted into SSA form."""
+        if block in self.processed_blocks:
+            return
+
+        self.processed_blocks.add(block)
+
+        # Try to seal the block right away if at all possible.
+        if self.can_seal(block):
+            self.seal_block(block)
+
+        block_definitions = list(block.definitions)
+        for definition in block_definitions:
+            value = definition.value
+            if cfg_ir.is_value_def(value, cfg_ir.LoadPointer):
+                # Read the variable from the definitions dictionary.
+                node_id = get_local_id(value.pointer)
+                if node_id is not None and node_id not in self.ineligible_locals:
+                    definition.redefine(self.read_variable(block, node_id))
+            elif isinstance(value, cfg_ir.StoreAtPointer):
+                node_id = get_local_id(value.pointer)
+                if node_id is not None and node_id not in self.ineligible_locals:
+                    # Write to the variable, and replace the definition by a 'None' literal.
+                    self.write_variable(block, node_id, value.value)
+                    definition.redefine(cfg_ir.Literal(None))
+            elif isinstance(value, cfg_ir.DeclareLocal):
+                node_id = value.variable.node_id
+                if node_id not in self.ineligible_locals:
+                    definition.redefine(cfg_ir.Literal(None))
+
+
+        # Mark the block as filled.
+        self.filled_blocks.add(block)
+
+        # Seal all sealable blocks.
+        self.seal_all_sealable_blocks()
+
+        # Fill successor blocks.
+        for branch in block.flow.branches():
+            self.fill_block(branch.block)
+
+    def update_block_branches(self, block):
+        """Appends arguments to the given block's flow instruction's branches, if necessary."""
+        for branch in block.flow.branches():
+            # Find all pairs phis which are defined in the branch target block.
+            applicable_pairs = [
+                (phi_def, operand_def)
+                for phi_def, operand_def in self.extra_phi_operands[block].items()
+                if phi_def.block == branch.block]
+
+            if len(applicable_pairs) == 0:
+                # We might as well early-out here.
+                continue
+
+            # Sort the pairs by block parameter index.
+            sorted_pairs = sorted(
+                applicable_pairs,
+                key=lambda (phi_def, _): phi_def.block.parameters.index(phi_def))
+
+            # Append arguments to the branch.
+            for _, arg in sorted_pairs:
+                branch.arguments.append(arg)

File diff suppressed because it is too large
+ 1022 - 0
kernel/modelverse_jit/cfg_to_tree.py


+ 198 - 34
kernel/modelverse_jit/intrinsics.py

@@ -1,6 +1,8 @@
-import jit
-import tree_ir
 import time
 import time
+import modelverse_jit.jit as jit
+import modelverse_jit.tree_ir as tree_ir
+import modelverse_jit.cfg_ir as cfg_ir
+import modelverse_jit.runtime as jit_runtime
 
 
 BINARY_INTRINSICS = {
 BINARY_INTRINSICS = {
     'value_eq' : '==',
     'value_eq' : '==',
@@ -60,22 +62,22 @@ def create_get_length(expression):
 # get them right.
 # get them right.
 # pylint: disable=I0011,C0103
 # pylint: disable=I0011,C0103
 def __set_add(a, b):
 def __set_add(a, b):
-    tmp = tree_ir.StoreLocalInstruction(None, a)
+    store_a, load_a = tree_ir.evaluate_and_load(a)
     return tree_ir.create_block(
     return tree_ir.create_block(
-        tmp,
-        tree_ir.CreateEdgeInstruction(tmp.create_load(), b),
-        tmp.create_load())
+        store_a,
+        tree_ir.CreateEdgeInstruction(load_a, b),
+        load_a)
 
 
 def __dict_add(a, b, c):
 def __dict_add(a, b, c):
-    a_tmp = tree_ir.StoreLocalInstruction(None, a)
-    b_tmp = tree_ir.StoreLocalInstruction(None, b)
+    store_a, load_a = tree_ir.evaluate_and_load(a)
+    store_b, load_b = tree_ir.evaluate_and_load(b)
     return tree_ir.create_block(
     return tree_ir.create_block(
-        a_tmp,
-        b_tmp,
+        store_a,
+        store_b,
         tree_ir.CreateEdgeInstruction(
         tree_ir.CreateEdgeInstruction(
-            tree_ir.CreateEdgeInstruction(a_tmp.create_load(), c),
-            b_tmp.create_load()),
-        a_tmp.create_load())
+            tree_ir.CreateEdgeInstruction(load_a, c),
+            load_b),
+        load_a)
 
 
 def __list_read(a, b):
 def __list_read(a, b):
     # The statements in this function generate the following code:
     # The statements in this function generate the following code:
@@ -87,17 +89,17 @@ def __list_read(a, b):
     #     raise Exception("List read out of bounds: %s" % b_value)
     #     raise Exception("List read out of bounds: %s" % b_value)
     # result
     # result
 
 
-    a_tmp = tree_ir.StoreLocalInstruction(None, a)
+    store_a, load_a = tree_ir.evaluate_and_load(a)
     b_val = tree_ir.StoreLocalInstruction(
     b_val = tree_ir.StoreLocalInstruction(
         None,
         None,
         tree_ir.ReadValueInstruction(b))
         tree_ir.ReadValueInstruction(b))
     result = tree_ir.StoreLocalInstruction(
     result = tree_ir.StoreLocalInstruction(
         None,
         None,
         tree_ir.ReadDictionaryValueInstruction(
         tree_ir.ReadDictionaryValueInstruction(
-            a_tmp.create_load(), b_val.create_load()))
+            load_a.create_load(), b_val.create_load()))
 
 
     return tree_ir.create_block(
     return tree_ir.create_block(
-        a_tmp,
+        store_a,
         b_val,
         b_val,
         result,
         result,
         tree_ir.SelectInstruction(
         tree_ir.SelectInstruction(
@@ -112,7 +114,7 @@ def __list_read(a, b):
                         tree_ir.LiteralInstruction('List read out of bounds: %s'),
                         tree_ir.LiteralInstruction('List read out of bounds: %s'),
                         '%',
                         '%',
                         b_val.create_load())])),
                         b_val.create_load())])),
-            tree_ir.NopInstruction()),
+            tree_ir.EmptyInstruction()),
         result.create_load())
         result.create_load())
 
 
 def __list_append(a, b):
 def __list_append(a, b):
@@ -124,17 +126,18 @@ def __list_append(a, b):
     # _ = yield [("CD", [a_tmp, len(a_outgoing), b_tmp])]
     # _ = yield [("CD", [a_tmp, len(a_outgoing), b_tmp])]
     # a
     # a
 
 
-    a_tmp = tree_ir.StoreLocalInstruction(None, a)
-    b_tmp = tree_ir.StoreLocalInstruction(None, b)
+    store_a, load_a = tree_ir.evaluate_and_load(a)
+    store_b, load_b = tree_ir.evaluate_and_load(b)
     return tree_ir.create_block(
     return tree_ir.create_block(
-        a_tmp,
+        store_a,
+        store_b,
         tree_ir.CreateDictionaryEdgeInstruction(
         tree_ir.CreateDictionaryEdgeInstruction(
-            a_tmp.create_load(),
+            load_a,
             create_get_length(
             create_get_length(
                 tree_ir.ReadOutgoingEdgesInstruction(
                 tree_ir.ReadOutgoingEdgesInstruction(
-                    a_tmp.create_load())),
-            b_tmp),
-        a_tmp.create_load())
+                    load_a)),
+            load_b),
+        load_a)
 
 
 def __log(a):
 def __log(a):
     # Original definition:
     # Original definition:
@@ -144,18 +147,29 @@ def __log(a):
     #     print("== LOG == " + str(a_value))
     #     print("== LOG == " + str(a_value))
     #     raise PrimitiveFinished(a)
     #     raise PrimitiveFinished(a)
 
 
-    a_tmp = tree_ir.StoreLocalInstruction(None, a)
+    store_a, load_a = tree_ir.evaluate_and_load(a)
     return tree_ir.CompoundInstruction(
     return tree_ir.CompoundInstruction(
         tree_ir.create_block(
         tree_ir.create_block(
-            a_tmp,
+            store_a,
             tree_ir.PrintInstruction(
             tree_ir.PrintInstruction(
                 tree_ir.BinaryInstruction(
                 tree_ir.BinaryInstruction(
                     tree_ir.LiteralInstruction("== LOG == "),
                     tree_ir.LiteralInstruction("== LOG == "),
                     '+',
                     '+',
                     tree_ir.CallInstruction(
                     tree_ir.CallInstruction(
                         tree_ir.LoadGlobalInstruction('str'),
                         tree_ir.LoadGlobalInstruction('str'),
-                        [tree_ir.ReadValueInstruction(a_tmp.create_load())])))),
-        a_tmp.create_load())
+                        [tree_ir.ReadValueInstruction(load_a)])))),
+        load_a)
+
+def __read_nr_out(a):
+    # Original definition:
+    #
+    # def read_nr_out(a, **remainder):
+    #     outgoing, = yield [("RO", [a])]
+    #     result, = yield [("CNV", [len(outgoing)])]
+    #     raise PrimitiveFinished(result)
+
+    return tree_ir.CreateNodeWithValueInstruction(
+        create_get_length(tree_ir.ReadOutgoingEdgesInstruction(a)))
 
 
 MISC_INTRINSICS = {
 MISC_INTRINSICS = {
     # Reference equality
     # Reference equality
@@ -235,18 +249,20 @@ MISC_INTRINSICS = {
                 'is not',
                 'is not',
                 tree_ir.LiteralInstruction(None))),
                 tree_ir.LiteralInstruction(None))),
 
 
+    'read_nr_out' : __read_nr_out,
+
     # read_root
     # read_root
     'read_root' :
     'read_root' :
         lambda:
         lambda:
         tree_ir.LoadIndexInstruction(
         tree_ir.LoadIndexInstruction(
-            tree_ir.LoadLocalInstruction(jit.KWARGS_PARAMETER_NAME),
+            tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
             tree_ir.LiteralInstruction('root')),
             tree_ir.LiteralInstruction('root')),
 
 
     # read_taskroot
     # read_taskroot
     'read_taskroot' :
     'read_taskroot' :
         lambda:
         lambda:
         tree_ir.LoadIndexInstruction(
         tree_ir.LoadIndexInstruction(
-            tree_ir.LoadLocalInstruction(jit.KWARGS_PARAMETER_NAME),
+            tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
             tree_ir.LiteralInstruction('task_root')),
             tree_ir.LiteralInstruction('task_root')),
 
 
     # Dictionary operations
     # Dictionary operations
@@ -261,15 +277,13 @@ MISC_INTRINSICS = {
             a, tree_ir.ReadValueInstruction(b)),
             a, tree_ir.ReadValueInstruction(b)),
 
 
     'dict_add' : __dict_add,
     'dict_add' : __dict_add,
+    'dict_len' : __read_nr_out,
 
 
     # Set operations
     # Set operations
     'set_add' : __set_add,
     'set_add' : __set_add,
 
 
     # List operations
     # List operations
-    'list_len' :
-        lambda a:
-        tree_ir.CreateNodeWithValueInstruction(
-            create_get_length(tree_ir.ReadOutgoingEdgesInstruction(a))),
+    'list_len' : __read_nr_out,
 
 
     'list_read' : __list_read,
     'list_read' : __list_read,
     'list_append' : __list_append,
     'list_append' : __list_append,
@@ -278,6 +292,154 @@ MISC_INTRINSICS = {
     'log' : __log
     'log' : __log
 }
 }
 
 
+def __read_nr_out_cfg(original_def, a):
+    # Original definition:
+    #
+    # def read_nr_out(a, **remainder):
+    #     outgoing, = yield [("RO", [a])]
+    #     result, = yield [("CNV", [len(outgoing)])]
+    #     raise PrimitiveFinished(result)
+
+    original_def.redefine(
+        cfg_ir.CreateNode(
+            original_def.insert_before(
+                cfg_ir.create_pure_simple_call(
+                    'len',
+                    original_def.insert_before(
+                        cfg_ir.create_read_outgoing_edges(a))))))
+
+def __dict_in_cfg(original_def, a, b):
+    # Original definition:
+    #
+    # def dict_in(a, b, **remainder):
+    #     b_value, = yield [("RV", [b])]
+    #     value, = yield [("RD", [a, b_value])]
+    #     is_in = value is not None
+    #     result, = yield [("CNV", [is_in])]
+    #     raise PrimitiveFinished(result)
+
+    original_def.redefine(
+        cfg_ir.CreateNode(
+            original_def.insert_before(
+                cfg_ir.Binary(
+                    original_def.insert_before(
+                        cfg_ir.create_read_dict_value(
+                            a, original_def.insert_before(cfg_ir.Read(b)))),
+                    'is not',
+                    original_def.insert_before(cfg_ir.Literal(None))))))
+
+def __dict_in_node_cfg(original_def, a, b):
+    # Original definition:
+    #
+    # def dict_in_node(a, b, **remainder):
+    #     value, = yield [("RDN", [a, b])]
+    #     result, = yield [("CNV", [value is not None])]
+    #     raise PrimitiveFinished(result)
+
+    original_def.redefine(
+        cfg_ir.CreateNode(
+            original_def.insert_before(
+                cfg_ir.Binary(
+                    original_def.insert_before(cfg_ir.create_read_dict_node(a, b)),
+                    'is not',
+                    original_def.insert_before(cfg_ir.Literal(None))))))
+
+def __dict_read_cfg(original_def, a, b):
+    # Original definition:
+    #
+    # def dict_read(a, b, **remainder):
+    #     b_value, = yield [("RV", [b])]
+    #     result, = yield [("RD", [a, b_value])]
+    #     raise PrimitiveFinished(result)
+
+    original_def.redefine(
+        cfg_ir.create_read_dict_value(
+            a,
+            original_def.insert_before(cfg_ir.Read(b))))
+
+MISC_CFG_INTRINSICS = {
+    # Reference equality
+    'element_eq' :
+        lambda original_def, a, b:
+        original_def.redefine(
+            cfg_ir.CreateNode(
+                original_def.insert_before(
+                    cfg_ir.Binary(a, '==', b)))),
+    'element_neq' :
+        lambda original_def, a, b:
+        original_def.redefine(
+            cfg_ir.CreateNode(
+                original_def.insert_before(
+                    cfg_ir.Binary(a, '!=', b)))),
+
+
+    # String operations
+    'string_get' :
+        lambda original_def, a, b:
+        original_def.redefine(
+            cfg_ir.CreateNode(
+                original_def.insert_before(
+                    cfg_ir.create_index(
+                        original_def.insert_before(cfg_ir.Read(a)),
+                        original_def.insert_before(cfg_ir.Read(b)))))),
+    'string_len' :
+        lambda original_def, a:
+        original_def.redefine(
+            cfg_ir.CreateNode(
+                original_def.insert_before(
+                    cfg_ir.create_pure_simple_call(
+                        'len',
+                        original_def.insert_before(cfg_ir.Read(a)))))),
+    'string_join' :
+        lambda original_def, a, b:
+        original_def.redefine(
+            cfg_ir.CreateNode(
+                original_def.insert_before(
+                    cfg_ir.Binary(
+                        original_def.insert_before(
+                            cfg_ir.create_pure_simple_call(
+                                'str',
+                                original_def.insert_before(cfg_ir.Read(a)))),
+                        '+',
+                        original_def.insert_before(
+                            cfg_ir.create_pure_simple_call(
+                                'str',
+                                original_def.insert_before(cfg_ir.Read(b)))))))),
+    'string_startswith' :
+        lambda original_def, a, b:
+        original_def.redefine(
+            cfg_ir.CreateNode(
+                original_def.insert_before(
+                    cfg_ir.DirectFunctionCall(
+                        'startswith',
+                        [('self', original_def.insert_before(cfg_ir.Read(a))),
+                         ('substring', original_def.insert_before(cfg_ir.Read(b)))],
+                        calling_convention=cfg_ir.SELF_POSITIONAL_CALLING_CONVENTION,
+                        has_value=True, has_side_effects=False)))),
+
+    # State creation
+    'create_node' :
+        lambda original_def:
+        original_def.redefine(
+            cfg_ir.CreateNode(original_def.insert_before(cfg_ir.Literal(None)))),
+    'create_value' :
+        lambda original_def, a:
+        original_def.redefine(
+            cfg_ir.CreateNode(original_def.insert_before(cfg_ir.Read(a)))),
+
+    # State reads
+    'read_nr_out' : __read_nr_out_cfg,
+
+    # Dictionary operations
+    'dict_len' : __read_nr_out_cfg,
+    'dict_read' : __dict_read_cfg,
+    'dict_in' : __dict_in_cfg,
+    'dict_in_node' : __dict_in_node_cfg,
+
+    # List operations
+    'list_len' : __read_nr_out_cfg
+}
+
 def register_time_intrinsic(target_jit):
 def register_time_intrinsic(target_jit):
     """Registers the time() intrinsic with the given JIT."""
     """Registers the time() intrinsic with the given JIT."""
     import_name = target_jit.import_value(time.time, 'time')
     import_name = target_jit.import_value(time.time, 'time')
@@ -298,5 +460,7 @@ def register_intrinsics(target_jit):
         target_jit.register_cast_intrinsic(key, value)
         target_jit.register_cast_intrinsic(key, value)
     for (key, value) in MISC_INTRINSICS.items():
     for (key, value) in MISC_INTRINSICS.items():
         target_jit.register_intrinsic(key, value)
         target_jit.register_intrinsic(key, value)
+    for (key, value) in MISC_CFG_INTRINSICS.items():
+        target_jit.register_cfg_intrinsic(key, value)
 
 
     register_time_intrinsic(target_jit)
     register_time_intrinsic(target_jit)

File diff suppressed because it is too large
+ 632 - 777
kernel/modelverse_jit/jit.py


+ 74 - 5
kernel/modelverse_jit/runtime.py

@@ -10,6 +10,59 @@ MUTABLE_FUNCTION_KEY = "mutable"
 FUNCTION_BODY_KEY = "body"
 FUNCTION_BODY_KEY = "body"
 """A dictionary key for function bodies."""
 """A dictionary key for function bodies."""
 
 
+KWARGS_PARAMETER_NAME = "kwargs"
+"""The name of the kwargs parameter in jitted functions."""
+
+CALL_FUNCTION_NAME = "__call_function"
+"""The name of the '__call_function' function, in the jitted function scope."""
+
+GET_INPUT_FUNCTION_NAME = "__get_input"
+"""The name of the '__get_input' function, in the jitted function scope."""
+
+JIT_THUNK_CONSTANT_FUNCTION_NAME = "__jit_thunk_constant_function"
+"""The name of the jit_thunk_constant_function function in the JIT's global context."""
+
+JIT_THUNK_GLOBAL_FUNCTION_NAME = "__jit_thunk_global"
+"""The name of the jit_thunk_global function in the JIT's global context."""
+
+JIT_REJIT_FUNCTION_NAME = "__jit_rejit"
+"""The name of the rejit function in the JIT's global context."""
+
+JIT_COMPILE_FUNCTION_BODY_FAST_FUNCTION_NAME = "__jit_compile_function_body_fast"
+"""The name of the compile_function_body_fast function in the JIT's global context."""
+
+UNREACHABLE_FUNCTION_NAME = "__unreachable"
+"""The name of the unreachable function in the JIT's global context."""
+
+LOCALS_NODE_NAME = "jit_locals"
+"""The name of the node that is connected to all JIT locals in a given function call."""
+
+LOCALS_EDGE_NAME = "jit_locals_edge"
+"""The name of the edge that connects the LOCALS_NODE_NAME node to a user root."""
+
+GLOBAL_NOT_FOUND_MESSAGE_FORMAT = "Not found as global: %s"
+"""The format of the 'not found as global' message. Takes a single argument."""
+
+BASELINE_JIT_ORIGIN_NAME = "baseline-jit"
+"""The origin name for functions that were produced by the baseline JIT."""
+
+FAST_JIT_ORIGIN_NAME = "fast-jit"
+"""The origin name for functions that were produced by the fast JIT."""
+
+def format_stack_frame(function_name, debug_info, origin='unknown'):
+    """Formats a stack frame, which consists of a function name, debug
+       information and an origin."""
+    if function_name is None:
+        function_name = 'unknown function'
+    if debug_info is None:
+        debug_info = '[unknown location] '
+
+    return '%sin %s (%s)' % (debug_info, function_name, origin)
+
+def format_trace_message(debug_info, function_name, origin='unknown'):
+    """Creates a formatted trace message."""
+    return 'TRACE: %s' % format_stack_frame(function_name, debug_info, origin)
+
 def call_function(function_id, named_arguments, **kwargs):
 def call_function(function_id, named_arguments, **kwargs):
     """Runs the function with the given id, passing it the specified argument dictionary."""
     """Runs the function with the given id, passing it the specified argument dictionary."""
     task_root = kwargs['task_root']
     task_root = kwargs['task_root']
@@ -22,9 +75,9 @@ def call_function(function_id, named_arguments, **kwargs):
     # frame.
     # frame.
     def handle_jit_failed(_):
     def handle_jit_failed(_):
         """Interprets the function."""
         """Interprets the function."""
-        interpreter_args = {'function_id' : function_id, 'named_arguments' : named_arguments}
+        interpreter_args = {'body_id' : body_id, 'named_arguments' : named_arguments}
         interpreter_args.update(kwargs)
         interpreter_args.update(kwargs)
-        yield [("TAIL_CALL_KWARGS", [interpret_function, interpreter_args])]
+        yield [("TAIL_CALL_KWARGS", [interpret_function_body, interpreter_args])]
 
 
     if is_mutable is not None:
     if is_mutable is not None:
         kernel.jit.mark_no_jit(body_id)
         kernel.jit.mark_no_jit(body_id)
@@ -45,10 +98,18 @@ def call_function(function_id, named_arguments, **kwargs):
 def interpret_function(function_id, named_arguments, **kwargs):
 def interpret_function(function_id, named_arguments, **kwargs):
     """Makes the interpreter run the function with the given id for the specified
     """Makes the interpreter run the function with the given id for the specified
        argument dictionary."""
        argument dictionary."""
+    body_id, = yield [("RD", [function_id, FUNCTION_BODY_KEY])]
+    args = {'body_id' : body_id, named_arguments : named_arguments}
+    args.update(kwargs)
+    yield [("TAIL_CALL_KWARGS", [interpret_function_body, args])]
+
+def interpret_function_body(body_id, named_arguments, **kwargs):
+    """Makes the interpreter run the function body with the given id for the specified
+       argument dictionary."""
     task_root = kwargs['task_root']
     task_root = kwargs['task_root']
     kernel = kwargs['mvk']
     kernel = kwargs['mvk']
-    task_frame, = yield [("RD", [task_root, "frame"])]
-    inst, body_id = yield [("RD", [task_frame, "IP"]), ("RD", [function_id, FUNCTION_BODY_KEY])]
+    user_frame, = yield [("RD", [task_root, "frame"])]
+    inst, = yield [("RD", [user_frame, "IP"])]
     kernel.jit.mark_entry_point(body_id)
     kernel.jit.mark_entry_point(body_id)
 
 
     # Create a new stack frame.
     # Create a new stack frame.
@@ -71,7 +132,7 @@ def interpret_function(function_id, named_arguments, **kwargs):
                            ("CD", [new_frame, "caller", inst]),
                            ("CD", [new_frame, "caller", inst]),
                            ("CD", [new_frame, "phase", new_phase]),
                            ("CD", [new_frame, "phase", new_phase]),
                            ("CD", [new_frame, "IP", body_id]),
                            ("CD", [new_frame, "IP", body_id]),
-                           ("CD", [new_frame, "prev", task_frame]),
+                           ("CD", [new_frame, "prev", user_frame]),
                            ("CD", [
                            ("CD", [
                                new_frame,
                                new_frame,
                                primitive_functions.EXCEPTION_RETURN_KEY,
                                primitive_functions.EXCEPTION_RETURN_KEY,
@@ -104,6 +165,14 @@ def interpret_function(function_id, named_arguments, **kwargs):
         # An instruction has completed. Forward it.
         # An instruction has completed. Forward it.
         yield result
         yield result
 
 
+class UnreachableCodeException(Exception):
+    """The type of exception that is thrown when supposedly unreachable code is executed."""
+    pass
+
+def unreachable():
+    """Marks unreachable code."""
+    raise UnreachableCodeException('An unreachable statement was reached.')
+
 def get_input(**parameters):
 def get_input(**parameters):
     """Retrieves input."""
     """Retrieves input."""
     mvk = parameters["mvk"]
     mvk = parameters["mvk"]

+ 52 - 0
kernel/modelverse_jit/source_map.py

@@ -0,0 +1,52 @@
+"""Defines source maps: dictionaries that map lines in generated source to debug information."""
+
+class SourceMap(object):
+    """A source map, which converts generated source lines to debug information."""
+    def __init__(self):
+        self.lines = {}
+
+    def map_line(self, line_number, debug_info):
+        """Assigns the given debug information to the given line."""
+        self.lines[line_number] = debug_info
+
+    def get_debug_info(self, line_number):
+        """Gets the debug information for the given line number, or None if no debug info was
+           found."""
+        if line_number in self.lines:
+            return self.lines[line_number]
+        else:
+            return None
+
+    def __str__(self):
+        return '\n'.join(
+            ['%d: %s' % pair
+             for pair in sorted(self.lines.items(), key=lambda (key, _): key)])
+
+class SourceMapBuilder(object):
+    """A type of object that makes it easy to build source maps for hierarchical instructions."""
+    def __init__(self, initial_line_number=0):
+        self.source_map = SourceMap()
+        self.debug_info_stack = []
+        self.line_number = initial_line_number - 1
+
+    def push_debug_info(self, debug_info):
+        """Informs the source map that subsequent lines of code will have the given debug
+           information associated with them."""
+        self.debug_info_stack.append(debug_info)
+
+    def pop_debug_info(self):
+        """Informs the source map that the debug information that was pushed last should
+           be discarded in favor of the debug information that was pushed onto the stack
+           just prior to it."""
+        return self.debug_info_stack.pop()
+
+    def append_line(self):
+        """Has the source map builder increment its line number counter, and assigns the debug
+           information that is at the top of the debug information stack to that line."""
+        self.line_number += 1
+        if len(self.debug_info_stack) > 0:
+            self.source_map.map_line(self.line_number, self.debug_info_stack[-1])
+
+    def __str__(self):
+        return str(self.source_map)
+

+ 467 - 79
kernel/modelverse_jit/tree_ir.py

@@ -28,6 +28,8 @@
 # Let's just agree to disagree on map vs list comprehensions, pylint.
 # Let's just agree to disagree on map vs list comprehensions, pylint.
 # pylint: disable=I0011,W0141
 # pylint: disable=I0011,W0141
 
 
+import modelverse_jit.source_map as source_map
+
 NOP_LITERAL = None
 NOP_LITERAL = None
 """A literal that results in a nop during which execution may be interrupted
 """A literal that results in a nop during which execution may be interrupted
    when yielded."""
    when yielded."""
@@ -108,6 +110,10 @@ class Instruction(object):
            instruction if it is not None."""
            instruction if it is not None."""
         return None
         return None
 
 
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return True
+
     def generate_python_def(self, code_generator):
     def generate_python_def(self, code_generator):
         """Generates a Python statement that executes this instruction.
         """Generates a Python statement that executes this instruction.
            The statement is appended immediately to the code generator."""
            The statement is appended immediately to the code generator."""
@@ -150,7 +156,6 @@ class Instruction(object):
 
 
 class PythonGenerator(object):
 class PythonGenerator(object):
     """Generates Python code from instructions."""
     """Generates Python code from instructions."""
-
     def __init__(self, combine_state_definitions=True):
     def __init__(self, combine_state_definitions=True):
         self.code = []
         self.code = []
         self.state_definitions = []
         self.state_definitions = []
@@ -159,11 +164,14 @@ class PythonGenerator(object):
         self.indentation = 0
         self.indentation = 0
         self.result_name_dict = {}
         self.result_name_dict = {}
         self.combine_state_definitions = combine_state_definitions
         self.combine_state_definitions = combine_state_definitions
+        self.source_map_builder = source_map.SourceMapBuilder()
 
 
     def append(self, text):
     def append(self, text):
         """Appends the given string to this code generator."""
         """Appends the given string to this code generator."""
         self.flush_state_definitions()
         self.flush_state_definitions()
         self.code.append(text)
         self.code.append(text)
+        for _ in xrange(text.count('\n')):
+            self.source_map_builder.append_line()
 
 
     def append_indentation(self):
     def append_indentation(self):
         """Appends indentation to the code generator."""
         """Appends indentation to the code generator."""
@@ -225,7 +233,7 @@ class PythonGenerator(object):
             # Generate the rhs' definition.
             # Generate the rhs' definition.
             rhs.generate_python_def(self)
             rhs.generate_python_def(self)
             # Only perform an assignment if it's truly necessary.
             # Only perform an assignment if it's truly necessary.
-            if lhs_result_name != rhs_result_name:
+            if lhs_result_name != rhs_result_name or not rhs.has_result_temporary():
                 self.append_definition(lhs, rhs)
                 self.append_definition(lhs, rhs)
         else:
         else:
             self.append_definition(lhs, rhs)
             self.append_definition(lhs, rhs)
@@ -284,6 +292,9 @@ class EmptyInstruction(VoidInstruction):
         """Tells if this instruction requires a definition."""
         """Tells if this instruction requires a definition."""
         return False
         return False
 
 
+    def __repr__(self):
+        return "EmptyInstruction()"
+
 class SelectInstruction(Instruction):
 class SelectInstruction(Instruction):
     """Represents a select-instruction: an instruction that defines one of two
     """Represents a select-instruction: an instruction that defines one of two
        child instructions, and sets its result to the defined child's result."""
        child instructions, and sets its result to the defined child's result."""
@@ -315,14 +326,15 @@ class SelectInstruction(Instruction):
         condition, if_clause, else_clause = new_children
         condition, if_clause, else_clause = new_children
         return SelectInstruction(condition, if_clause, else_clause)
         return SelectInstruction(condition, if_clause, else_clause)
 
 
-    def generate_python_def(self, code_generator):
+    def generate_python_if(self, code_generator, is_elif=False):
         """Generates Python code for this instruction."""
         """Generates Python code for this instruction."""
         if_has_result = self.has_result()
         if_has_result = self.has_result()
         if self.condition.has_definition():
         if self.condition.has_definition():
             self.condition.generate_python_def(code_generator)
             self.condition.generate_python_def(code_generator)
 
 
         code_generator.append_line(
         code_generator.append_line(
-            'if ' + self.condition.generate_python_use(code_generator) + ':')
+            ('elif ' if is_elif else 'if ') +
+            self.condition.generate_python_use(code_generator) + ':')
         code_generator.increase_indentation()
         code_generator.increase_indentation()
         if if_has_result:
         if if_has_result:
             code_generator.append_move_definition(self, self.if_clause)
             code_generator.append_move_definition(self, self.if_clause)
@@ -331,13 +343,28 @@ class SelectInstruction(Instruction):
         code_generator.decrease_indentation()
         code_generator.decrease_indentation()
         else_has_def = self.else_clause.has_definition()
         else_has_def = self.else_clause.has_definition()
         if else_has_def or if_has_result:
         if else_has_def or if_has_result:
-            code_generator.append_line('else:')
-            code_generator.increase_indentation()
-            if if_has_result:
-                code_generator.append_move_definition(self, self.else_clause)
+            if (isinstance(self.else_clause, SelectInstruction) and
+                    not self.else_clause.condition.has_definition()):
+                self.else_clause.generate_python_if(code_generator, True)
+                if if_has_result:
+                    code_generator.increase_indentation()
+                    code_generator.append_definition(self, self.else_clause)
+                    code_generator.decrease_indentation()
             else:
             else:
-                self.else_clause.generate_python_def(code_generator)
-            code_generator.decrease_indentation()
+                code_generator.append_line('else:')
+                code_generator.increase_indentation()
+                if if_has_result:
+                    code_generator.append_move_definition(self, self.else_clause)
+                else:
+                    self.else_clause.generate_python_def(code_generator)
+                code_generator.decrease_indentation()
+
+    def generate_python_def(self, code_generator):
+        """Generates Python code for this instruction."""
+        return self.generate_python_if(code_generator)
+
+    def __repr__(self):
+        return "SelectInstruction(%r, %r, %r)" % (self.condition, self.if_clause, self.else_clause)
 
 
 class ReturnInstruction(VoidInstruction):
 class ReturnInstruction(VoidInstruction):
     """Represents a return-instruction."""
     """Represents a return-instruction."""
@@ -387,6 +414,9 @@ class ReturnInstruction(VoidInstruction):
             self.value.generate_python_use(code_generator) +
             self.value.generate_python_use(code_generator) +
             ')')
             ')')
 
 
+    def __repr__(self):
+        return "ReturnInstruction(%r)" % self.value
+
 class RaiseInstruction(VoidInstruction):
 class RaiseInstruction(VoidInstruction):
     """An instruction that raises an error."""
     """An instruction that raises an error."""
     def __init__(self, value):
     def __init__(self, value):
@@ -408,6 +438,9 @@ class RaiseInstruction(VoidInstruction):
         code_generator.append_line(
         code_generator.append_line(
             'raise ' + self.value.generate_python_use(code_generator))
             'raise ' + self.value.generate_python_use(code_generator))
 
 
+    def __repr__(self):
+        return "RaiseInstruction(%r)" % self.value
+
 class CallInstruction(Instruction):
 class CallInstruction(Instruction):
     """An instruction that performs a simple call."""
     """An instruction that performs a simple call."""
     def __init__(self, target, argument_list):
     def __init__(self, target, argument_list):
@@ -438,11 +471,15 @@ class CallInstruction(Instruction):
                 self.target.generate_python_use(code_generator),
                 self.target.generate_python_use(code_generator),
                 ', '.join([arg.generate_python_use(code_generator) for arg in self.argument_list])))
                 ', '.join([arg.generate_python_use(code_generator) for arg in self.argument_list])))
 
 
+    def __repr__(self):
+        return "CallInstruction(%r, %r)" % (self.target, self.argument_list)
+
 class PrintInstruction(VoidInstruction):
 class PrintInstruction(VoidInstruction):
     """An instruction that prints a value."""
     """An instruction that prints a value."""
     def __init__(self, argument):
     def __init__(self, argument):
         VoidInstruction.__init__(self)
         VoidInstruction.__init__(self)
         self.argument = argument
         self.argument = argument
+        assert isinstance(argument, Instruction)
 
 
     def get_children(self):
     def get_children(self):
         """Gets this instruction's sequence of child instructions."""
         """Gets this instruction's sequence of child instructions."""
@@ -462,6 +499,57 @@ class PrintInstruction(VoidInstruction):
             'print(%s)' % (
             'print(%s)' % (
                 self.argument.generate_python_use(code_generator)))
                 self.argument.generate_python_use(code_generator)))
 
 
+    def __repr__(self):
+        return "PrintInstruction(%r)" % self.argument
+
+class DebugInfoInstruction(Instruction):
+    """An instruction that defines debug information for its child instruction."""
+    def __init__(self, value, debug_info):
+        Instruction.__init__(self)
+        self.value = value
+        self.debug_info = debug_info
+
+    def has_definition_impl(self):
+        """Tells if this instruction requires a definition."""
+        return self.value.has_definition()
+
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return self.value.has_result_temporary()
+
+    def get_result_type_impl(self):
+        """Gets this instruction's result type."""
+        return self.value.get_result_type()
+
+    def get_result_name_override(self, code_generator):
+        """Gets a value that overrides the code generator's result name for this
+           instruction if it is not None."""
+        return self.value.get_result_name_override(code_generator)
+
+    def get_children(self):
+        """Gets this instruction's sequence of child instructions."""
+        return [self.value]
+
+    def create(self, new_children):
+        """Creates a new instruction of this type from the given sequence of child instructions."""
+        arg, = new_children
+        return DebugInfoInstruction(arg, self.debug_info)
+
+    def generate_python_def(self, code_generator):
+        """Generates Python code for this instruction."""
+        if self.has_definition():
+            code_generator.source_map_builder.push_debug_info(self.debug_info)
+            code_generator.append_move_definition(self, self.value)
+            code_generator.source_map_builder.pop_debug_info()
+
+    def generate_python_use(self, code_generator):
+        """Generates a Python expression that retrieves this instruction's
+           result. The expression is returned as a string."""
+        return self.value.generate_python_use(code_generator)
+
+    def __repr__(self):
+        return "DebugInfoInstruction(%r, %r)" % (self.value, self.debug_info)
+
 class BinaryInstruction(Instruction):
 class BinaryInstruction(Instruction):
     """An instruction that performs a binary operation."""
     """An instruction that performs a binary operation."""
     def __init__(self, lhs, operator, rhs):
     def __init__(self, lhs, operator, rhs):
@@ -492,6 +580,10 @@ class BinaryInstruction(Instruction):
         else:
         else:
             return self
             return self
 
 
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
     def generate_python_use(self, code_generator):
     def generate_python_use(self, code_generator):
         """Generates a Python expression that retrieves this instruction's
         """Generates a Python expression that retrieves this instruction's
            result. The expression is returned as a string."""
            result. The expression is returned as a string."""
@@ -503,15 +595,19 @@ class BinaryInstruction(Instruction):
     def generate_python_def(self, code_generator):
     def generate_python_def(self, code_generator):
         """Generates a Python statement that executes this instruction.
         """Generates a Python statement that executes this instruction.
            The statement is appended immediately to the code generator."""
            The statement is appended immediately to the code generator."""
-        if self.lhs.has_definition():
+        lhs_has_def, rhs_has_def = self.lhs.has_definition(), self.rhs.has_definition()
+        if lhs_has_def:
             self.lhs.generate_python_def(code_generator)
             self.lhs.generate_python_def(code_generator)
-            if self.rhs.has_definition():
+            if rhs_has_def:
                 self.rhs.generate_python_def(code_generator)
                 self.rhs.generate_python_def(code_generator)
-        elif self.rhs.has_definition():
+        elif rhs_has_def:
             self.rhs.generate_python_def(code_generator)
             self.rhs.generate_python_def(code_generator)
         else:
         else:
             code_generator.append_line('pass')
             code_generator.append_line('pass')
 
 
+    def __repr__(self):
+        return "BinaryInstruction(%r, %r, %r)" % (self.lhs, self.operator, self.rhs)
+
 class UnaryInstruction(Instruction):
 class UnaryInstruction(Instruction):
     """An instruction that performs a unary operation."""
     """An instruction that performs a unary operation."""
     def __init__(self, operator, operand):
     def __init__(self, operator, operand):
@@ -527,6 +623,10 @@ class UnaryInstruction(Instruction):
         """Gets this instruction's sequence of child instructions."""
         """Gets this instruction's sequence of child instructions."""
         return [self.operand]
         return [self.operand]
 
 
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
     def create(self, new_children):
     def create(self, new_children):
         """Creates a new instruction of this type from the given sequence of child instructions."""
         """Creates a new instruction of this type from the given sequence of child instructions."""
         operand, = new_children
         operand, = new_children
@@ -556,9 +656,11 @@ class UnaryInstruction(Instruction):
         else:
         else:
             code_generator.append_line('pass')
             code_generator.append_line('pass')
 
 
+    def __repr__(self):
+        return "UnaryInstruction(%r, %r)" % (self.operator, self.operand)
+
 class LoopInstruction(VoidInstruction):
 class LoopInstruction(VoidInstruction):
     """Represents a loop-instruction, which loops until broken."""
     """Represents a loop-instruction, which loops until broken."""
-
     def __init__(self, body):
     def __init__(self, body):
         VoidInstruction.__init__(self)
         VoidInstruction.__init__(self)
         self.body = body
         self.body = body
@@ -579,18 +681,61 @@ class LoopInstruction(VoidInstruction):
         self.body.generate_python_def(code_generator)
         self.body.generate_python_def(code_generator)
         code_generator.decrease_indentation()
         code_generator.decrease_indentation()
 
 
+    def __repr__(self):
+        return "LoopInstruction(%r)" % self.body
+
 class BreakInstruction(VoidInstruction):
 class BreakInstruction(VoidInstruction):
     """Represents a break-instruction."""
     """Represents a break-instruction."""
     def generate_python_def(self, code_generator):
     def generate_python_def(self, code_generator):
         """Generates Python code for this instruction."""
         """Generates Python code for this instruction."""
         code_generator.append_line('break')
         code_generator.append_line('break')
 
 
+    def __repr__(self):
+        return "BreakInstruction()"
+
 class ContinueInstruction(VoidInstruction):
 class ContinueInstruction(VoidInstruction):
     """Represents a continue-instruction."""
     """Represents a continue-instruction."""
     def generate_python_def(self, code_generator):
     def generate_python_def(self, code_generator):
         """Generates Python code for this instruction."""
         """Generates Python code for this instruction."""
         code_generator.append_line('continue')
         code_generator.append_line('continue')
 
 
+    def __repr__(self):
+        return "ContinueInstruction()"
+
+class IgnoreInstruction(VoidInstruction):
+    """Represents an instruction that evaluates its operand, and then discards
+       the result."""
+    def __init__(self, value):
+        VoidInstruction.__init__(self)
+        self.value = value
+
+    def has_definition_impl(self):
+        """Tells if this instruction requires a definition."""
+        return self.value.has_definition()
+
+    def get_children(self):
+        """Gets this instruction's sequence of child instructions."""
+        return [self.value]
+
+    def create(self, new_children):
+        """Creates a new instruction of this type from the given sequence of child instructions."""
+        value, = new_children
+        return IgnoreInstruction(value)
+
+    def simplify_node(self):
+        """Applies basic simplification to this instruction and its children."""
+        if not self.value.has_result():
+            return self.value
+        else:
+            return self
+
+    def generate_python_def(self, code_generator):
+        """Generates Python code for this instruction."""
+        self.value.generate_python_def(code_generator)
+
+    def __repr__(self):
+        return "IgnoreInstruction(%r)" % self.value
+
 class CompoundInstruction(Instruction):
 class CompoundInstruction(Instruction):
     """Represents an instruction that evaluates two other instructions
     """Represents an instruction that evaluates two other instructions
        in order, and returns the second instruction's result."""
        in order, and returns the second instruction's result."""
@@ -637,6 +782,9 @@ class CompoundInstruction(Instruction):
             self.first.generate_python_def(code_generator)
             self.first.generate_python_def(code_generator)
             self.second.generate_python_def(code_generator)
             self.second.generate_python_def(code_generator)
 
 
+    def __repr__(self):
+        return "CompoundInstruction(%r, %r)" % (self.first, self.second)
+
 class LiteralInstruction(Instruction):
 class LiteralInstruction(Instruction):
     """Represents an integer, floating-point, string or Boolean literal."""
     """Represents an integer, floating-point, string or Boolean literal."""
     def __init__(self, literal):
     def __init__(self, literal):
@@ -647,6 +795,10 @@ class LiteralInstruction(Instruction):
         """Tells if this instruction requires a definition."""
         """Tells if this instruction requires a definition."""
         return False
         return False
 
 
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
     def get_children(self):
     def get_children(self):
         """Gets this instruction's sequence of child instructions."""
         """Gets this instruction's sequence of child instructions."""
         return []
         return []
@@ -660,6 +812,9 @@ class LiteralInstruction(Instruction):
            result. The expression is returned as a string."""
            result. The expression is returned as a string."""
         return repr(self.literal)
         return repr(self.literal)
 
 
+    def __repr__(self):
+        return "LiteralInstruction(%r)" % self.literal
+
 class DictionaryLiteralInstruction(Instruction):
 class DictionaryLiteralInstruction(Instruction):
     """Constructs a dictionary literal."""
     """Constructs a dictionary literal."""
     def __init__(self, key_value_pairs):
     def __init__(self, key_value_pairs):
@@ -668,12 +823,18 @@ class DictionaryLiteralInstruction(Instruction):
 
 
     def get_children(self):
     def get_children(self):
         """Gets this instruction's sequence of child instructions."""
         """Gets this instruction's sequence of child instructions."""
-        return [val for _, val in self.key_value_pairs]
+        results = []
+        for key, val in self.key_value_pairs:
+            results.append(key)
+            results.append(val)
+        return results
 
 
     def create(self, new_children):
     def create(self, new_children):
         """Creates a new instruction of this type from the given sequence of child instructions."""
         """Creates a new instruction of this type from the given sequence of child instructions."""
-        keys = [k for k, _ in self.key_value_pairs]
-        return DictionaryLiteralInstruction(zip(keys, new_children))
+        new_kv_pairs = []
+        for i in xrange(len(self.key_value_pairs)):
+            new_kv_pairs.append((new_children[2 * i], new_children[2 * i + 1]))
+        return DictionaryLiteralInstruction(new_kv_pairs)
 
 
     def has_definition_impl(self):
     def has_definition_impl(self):
         """Tells if this instruction requires a definition."""
         """Tells if this instruction requires a definition."""
@@ -681,42 +842,92 @@ class DictionaryLiteralInstruction(Instruction):
             [key.has_definition() or val.has_definition()
             [key.has_definition() or val.has_definition()
              for key, val in self.key_value_pairs])
              for key, val in self.key_value_pairs])
 
 
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
     def simplify(self):
     def simplify(self):
         """Applies basic simplification to this instruction and its children."""
         """Applies basic simplification to this instruction and its children."""
         return DictionaryLiteralInstruction(
         return DictionaryLiteralInstruction(
             [(key.simplify(), val.simplify()) for key, val in self.key_value_pairs])
             [(key.simplify(), val.simplify()) for key, val in self.key_value_pairs])
 
 
-    def generate_dictionary_expr(self, code_generator):
-        """Generates an expression that creates this dictionary."""
+    def generate_python_def(self, code_generator):
+        """Generates a Python statement that executes this instruction.
+            The statement is appended immediately to the code generator."""
+        for key, val in self.key_value_pairs:
+            if key.has_definition():
+                key.generate_python_def(code_generator)
+            if val.has_definition():
+                val.generate_python_def(code_generator)
+
+    def generate_python_use(self, code_generator):
+        """Generates a Python expression that retrieves this instruction's
+           result. The expression is returned as a string."""
         return '{%s}' % ', '.join(
         return '{%s}' % ', '.join(
             ['%s : %s' % (
             ['%s : %s' % (
                 key.generate_python_use(code_generator),
                 key.generate_python_use(code_generator),
                 val.generate_python_use(code_generator))
                 val.generate_python_use(code_generator))
              for key, val in self.key_value_pairs])
              for key, val in self.key_value_pairs])
 
 
+    def __repr__(self):
+        return "DictionaryLiteralInstruction(%r)" % self.key_value_pairs
+
+class ListSliceInstruction(Instruction):
+    """Slices a list."""
+    def __init__(self, seq, start, end, step):
+        Instruction.__init__(self)
+        self.seq = seq
+        self.start = start
+        self.end = end
+        self.step = step
+
+    def get_children(self):
+        """Gets this instruction's sequence of child instructions."""
+        all_items = (self.seq, self.start, self.end, self.step)
+        return [item for item in all_items if item is not None]
+
+    def create(self, new_children):
+        """Creates a new instruction of this type from the given sequence of child instructions."""
+        # pylint: disable=I0011,E1120
+        args = []
+        i = 0
+        for old_item in (self.seq, self.start, self.end, self.step):
+            if old_item is None:
+                args.append(None)
+            else:
+                args.append(new_children[i])
+                i += 1
+
+        assert len(new_children) == i
+        assert len(args) == 4
+        return ListSliceInstruction(*args)
+
+    def has_definition_impl(self):
+        """Tells if this instruction requires a definition."""
+        return any([item.has_definition() for item in self.get_children()])
+
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
     def generate_python_def(self, code_generator):
     def generate_python_def(self, code_generator):
         """Generates a Python statement that executes this instruction.
         """Generates a Python statement that executes this instruction.
             The statement is appended immediately to the code generator."""
             The statement is appended immediately to the code generator."""
-        if self.has_definition():
-            for key, val in self.key_value_pairs:
-                if key.has_definition():
-                    key.generate_python_def(code_generator)
-                if val.has_definition():
-                    val.generate_python_def(code_generator)
-
-            code_generator.append_line('%s = %s' % (
-                code_generator.get_result_name(self),
-                self.generate_dictionary_expr(code_generator)))
-        else:
-            code_generator.append_line('pass')
+        for item in self.get_children():
+            if item.has_definition():
+                item.generate_python_def(code_generator)
 
 
     def generate_python_use(self, code_generator):
     def generate_python_use(self, code_generator):
         """Generates a Python expression that retrieves this instruction's
         """Generates a Python expression that retrieves this instruction's
            result. The expression is returned as a string."""
            result. The expression is returned as a string."""
-        if self.has_definition():
-            return code_generator.get_result_name(self)
-        else:
-            return self.generate_dictionary_expr(code_generator)
+        return '%s[%s:%s:%s]' % (
+            self.seq.generate_python_use(code_generator),
+            '' if self.start is None else self.start.generate_python_use(code_generator),
+            '' if self.end is None else self.end.generate_python_use(code_generator),
+            '' if self.step is None else self.step.generate_python_use(code_generator))
+
+    def __repr__(self):
+        return "ListSliceInstruction(%r, %r, %r, %r)" % (self.seq, self.start, self.end, self.step)
 
 
 class StateInstruction(Instruction):
 class StateInstruction(Instruction):
     """An instruction that accesses the modelverse state."""
     """An instruction that accesses the modelverse state."""
@@ -743,7 +954,6 @@ class StateInstruction(Instruction):
     def generate_python_def(self, code_generator):
     def generate_python_def(self, code_generator):
         """Generates a Python statement that executes this instruction.
         """Generates a Python statement that executes this instruction.
            The statement is appended immediately to the code generator."""
            The statement is appended immediately to the code generator."""
-
         args = self.get_arguments()
         args = self.get_arguments()
         for arg_i in args:
         for arg_i in args:
             if arg_i.has_definition():
             if arg_i.has_definition():
@@ -751,6 +961,9 @@ class StateInstruction(Instruction):
 
 
         code_generator.append_state_definition(self, self.get_opcode(), args)
         code_generator.append_state_definition(self, self.get_opcode(), args)
 
 
+    def __repr__(self):
+        return "StateInstruction(%s)" % ', '.join(map(repr, self.get_arguments()))
+
 class RunGeneratorFunctionInstruction(StateInstruction):
 class RunGeneratorFunctionInstruction(StateInstruction):
     """An instruction that runs a generator function."""
     """An instruction that runs a generator function."""
     def __init__(self, function, argument_dict, result_type=PRIMITIVE_RESULT_TYPE):
     def __init__(self, function, argument_dict, result_type=PRIMITIVE_RESULT_TYPE):
@@ -774,10 +987,27 @@ class RunGeneratorFunctionInstruction(StateInstruction):
 
 
 class RunTailGeneratorFunctionInstruction(StateInstruction):
 class RunTailGeneratorFunctionInstruction(StateInstruction):
     """An instruction that runs a generator function."""
     """An instruction that runs a generator function."""
-    def __init__(self, function, argument_dict):
+    def __init__(self, function, argument_dict, result_type=NO_RESULT_TYPE):
         StateInstruction.__init__(self)
         StateInstruction.__init__(self)
         self.function = function
         self.function = function
         self.argument_dict = argument_dict
         self.argument_dict = argument_dict
+        self.result_type_cache = result_type
+
+    def get_opcode(self):
+        """Gets the opcode for this state instruction."""
+        return "TAIL_CALL_KWARGS"
+
+    def get_arguments(self):
+        """Gets this state instruction's argument list."""
+        return [self.function, self.argument_dict]
+
+class RegisterDebugInfoInstruction(StateInstruction):
+    """An instruction that sends a DEBUG_INFO request to the request handler."""
+    def __init__(self, function_name, function_source_map, function_origin):
+        StateInstruction.__init__(self)
+        self.function_name = function_name
+        self.function_source_map = function_source_map
+        self.function_origin = function_origin
 
 
     def get_result_type_impl(self):
     def get_result_type_impl(self):
         """Gets the type of value produced by this instruction."""
         """Gets the type of value produced by this instruction."""
@@ -785,11 +1015,11 @@ class RunTailGeneratorFunctionInstruction(StateInstruction):
 
 
     def get_opcode(self):
     def get_opcode(self):
         """Gets the opcode for this state instruction."""
         """Gets the opcode for this state instruction."""
-        return "TAIL_CALL_KWARGS"
+        return "DEBUG_INFO"
 
 
     def get_arguments(self):
     def get_arguments(self):
         """Gets this state instruction's argument list."""
         """Gets this state instruction's argument list."""
-        return [self.function, self.argument_dict]
+        return [self.function_name, self.function_source_map, self.function_origin]
 
 
 class VariableName(object):
 class VariableName(object):
     """A data structure that unifies names across instructions that access the
     """A data structure that unifies names across instructions that access the
@@ -802,6 +1032,12 @@ class VariableName(object):
            instruction if it is not None."""
            instruction if it is not None."""
         return self.name
         return self.name
 
 
+    def __str__(self):
+        return self.name
+
+    def __repr__(self):
+        return 'VariableName(%r)' % self.name
+
 class VariableInstruction(Instruction):
 class VariableInstruction(Instruction):
     """A base class for instructions that access variables."""
     """A base class for instructions that access variables."""
     def __init__(self, name):
     def __init__(self, name):
@@ -863,6 +1099,60 @@ class StoreLocalInstruction(LocalInstruction):
            The statement is appended immediately to the code generator."""
            The statement is appended immediately to the code generator."""
         code_generator.append_move_definition(self, self.value)
         code_generator.append_move_definition(self, self.value)
 
 
+    def __repr__(self):
+        return 'StoreLocalInstruction(%r, %r)' % (self.name, self.value)
+
+class TupleStoreLocalInstruction(VoidInstruction):
+    """Assigns a number of values to a number of variables."""
+    def __init__(self, name_value_pairs):
+        VoidInstruction.__init__(self)
+        self.name_value_pairs = name_value_pairs
+
+    def get_children(self):
+        """Gets this instruction's sequence of child instructions."""
+        return [val for _, val in self.name_value_pairs]
+
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
+    def create(self, new_children):
+        """Creates a new instruction of this type from the given sequence of child instructions."""
+        new_name_value_pairs = [
+            (name, new_value) for (name, _), new_value in zip(self.name_value_pairs, new_children)]
+        return TupleStoreLocalInstruction(new_name_value_pairs)
+
+    def generate_python_def(self, code_generator):
+        """Generates a Python statement that executes this instruction.
+           The statement is appended immediately to the code generator."""
+        tuple_lhs = []
+        tuple_rhs = []
+        for name, value in self.name_value_pairs:
+            if isinstance(name, str) or isinstance(name, unicode) or name is None:
+                variable = VariableName(name)
+            else:
+                variable = name
+
+            # Retrieve the result name for the variable.
+            var_result_name = code_generator.get_result_name(variable)
+            # Encourage the value to take on the same result name as the variable.
+            value_result_name = code_generator.get_result_name(value, var_result_name)
+            if value.has_definition():
+                # Generate the value' definition.
+                value.generate_python_def(code_generator)
+
+            # Only perform an assignment if it's truly necessary.
+            if var_result_name != value_result_name or not value.has_result_temporary():
+                tuple_lhs.append(var_result_name)
+                tuple_rhs.append(value.generate_python_use(code_generator))
+
+        if len(tuple_lhs) > 0:
+            code_generator.append_line(
+                '%s = %s' % (', '.join(tuple_lhs), ', '.join(tuple_rhs)))
+
+    def __repr__(self):
+        return 'TupleStoreLocalInstruction(%r)' % (self.name_value_pairs)
+
 class LoadLocalInstruction(LocalInstruction):
 class LoadLocalInstruction(LocalInstruction):
     """An instruction that loads a value from a local variable."""
     """An instruction that loads a value from a local variable."""
     def has_definition_impl(self):
     def has_definition_impl(self):
@@ -877,6 +1167,9 @@ class LoadLocalInstruction(LocalInstruction):
         """Creates a new instruction of this type from the given sequence of child instructions."""
         """Creates a new instruction of this type from the given sequence of child instructions."""
         return self
         return self
 
 
+    def __repr__(self):
+        return 'LoadLocalInstruction(%r)' % self.name
+
 class DefineFunctionInstruction(VariableInstruction):
 class DefineFunctionInstruction(VariableInstruction):
     """An instruction that defines a function."""
     """An instruction that defines a function."""
     def __init__(self, name, parameter_list, body):
     def __init__(self, name, parameter_list, body):
@@ -902,6 +1195,9 @@ class DefineFunctionInstruction(VariableInstruction):
         self.body.generate_python_def(code_generator)
         self.body.generate_python_def(code_generator)
         code_generator.decrease_indentation()
         code_generator.decrease_indentation()
 
 
+    def __repr__(self):
+        return 'DefineFunctionInstruction(%r, %r, %r)' % (self.name, self.parameter_list, self.body)
+
 class LocalExistsInstruction(LocalInstruction):
 class LocalExistsInstruction(LocalInstruction):
     """An instruction that checks if a local variable exists."""
     """An instruction that checks if a local variable exists."""
     def has_definition_impl(self):
     def has_definition_impl(self):
@@ -921,6 +1217,9 @@ class LocalExistsInstruction(LocalInstruction):
            result. The expression is returned as a string."""
            result. The expression is returned as a string."""
         return "'%s' in locals()" % self.get_result_name_override(code_generator)
         return "'%s' in locals()" % self.get_result_name_override(code_generator)
 
 
+    def __repr__(self):
+        return 'LocalExistsInstruction(%r)' % self.name
+
 class LoadGlobalInstruction(VariableInstruction):
 class LoadGlobalInstruction(VariableInstruction):
     """An instruction that loads a value from a global variable."""
     """An instruction that loads a value from a global variable."""
     def has_definition_impl(self):
     def has_definition_impl(self):
@@ -935,6 +1234,58 @@ class LoadGlobalInstruction(VariableInstruction):
         """Creates a new instruction of this type from the given sequence of child instructions."""
         """Creates a new instruction of this type from the given sequence of child instructions."""
         return self
         return self
 
 
+    def __repr__(self):
+        return 'LoadGlobalInstruction(%r)' % self.name
+
+class StoreGlobalInstruction(VariableInstruction):
+    """An instruction that assigns a value to a global variable."""
+    def __init__(self, name, value):
+        VariableInstruction.__init__(self, name)
+        self.value = value
+
+    def get_children(self):
+        """Gets this instruction's sequence of child instructions."""
+        return [self.value]
+
+    def create(self, new_children):
+        """Creates a new instruction of this type from the given sequence of child instructions."""
+        val, = new_children
+        return StoreGlobalInstruction(self.name, val)
+
+    def generate_python_def(self, code_generator):
+        """Generates a Python statement that executes this instruction.
+           The statement is appended immediately to the code generator."""
+        code_generator.append_move_definition(self, self.value)
+
+    def __repr__(self):
+        return 'StoreGlobalInstruction(%r, %r)' % (self.name, self.value)
+
+class DeclareGlobalInstruction(VariableInstruction):
+    """An instruction that declares a name as a global variable."""
+    def get_children(self):
+        """Gets this instruction's sequence of child instructions."""
+        return []
+
+    def get_result_type_impl(self):
+        """Gets the type of value produced by this instruction."""
+        return NO_RESULT_TYPE
+
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
+    def create(self, new_children):
+        """Creates a new instruction of this type from the given sequence of child instructions."""
+        return self
+
+    def generate_python_def(self, code_generator):
+        """Generates a Python statement that executes this instruction.
+           The statement is appended immediately to the code generator."""
+        code_generator.append_line('global %s' % self.name)
+
+    def __repr__(self):
+        return 'DeclareGlobalInstruction(%r)' % self.name
+
 class LoadIndexInstruction(Instruction):
 class LoadIndexInstruction(Instruction):
     """An instruction that produces a value by indexing a specified expression with
     """An instruction that produces a value by indexing a specified expression with
        a given key."""
        a given key."""
@@ -945,12 +1296,16 @@ class LoadIndexInstruction(Instruction):
 
 
     def has_definition_impl(self):
     def has_definition_impl(self):
         """Tells if this instruction requires a definition."""
         """Tells if this instruction requires a definition."""
-        return False
+        return self.indexed.has_definition() or self.key.has_definition()
 
 
     def get_children(self):
     def get_children(self):
         """Gets this instruction's sequence of child instructions."""
         """Gets this instruction's sequence of child instructions."""
         return [self.indexed, self.key]
         return [self.indexed, self.key]
 
 
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
     def create(self, new_children):
     def create(self, new_children):
         """Creates a new instruction of this type from the given sequence of child instructions."""
         """Creates a new instruction of this type from the given sequence of child instructions."""
         indexed, key = new_children
         indexed, key = new_children
@@ -959,15 +1314,25 @@ class LoadIndexInstruction(Instruction):
     def generate_python_use(self, code_generator):
     def generate_python_use(self, code_generator):
         """Generates a Python expression that retrieves this instruction's
         """Generates a Python expression that retrieves this instruction's
            result. The expression is returned as a string."""
            result. The expression is returned as a string."""
-        if self.indexed.has_definition():
+        return "%s[%s]" % (
+            self.indexed.generate_python_use(code_generator),
+            self.key.generate_python_use(code_generator))
+
+    def generate_python_def(self, code_generator):
+        """Generates a Python statement that executes this instruction.
+           The statement is appended immediately to the code generator."""
+        indexed_has_def, key_has_def = self.indexed.has_definition(), self.key.has_definition()
+        if indexed_has_def:
             self.indexed.generate_python_def(code_generator)
             self.indexed.generate_python_def(code_generator)
 
 
-        if self.key.has_definition():
+        if key_has_def:
             self.key.generate_python_def(code_generator)
             self.key.generate_python_def(code_generator)
 
 
-        return "%s[%s]" % (
-            self.indexed.generate_python_use(code_generator),
-            self.key.generate_python_use(code_generator))
+        if not indexed_has_def and not key_has_def:
+            code_generator.append_line('pass')
+
+    def __repr__(self):
+        return 'LoadIndexInstruction(%r, %r)' % (self.indexed, self.key)
 
 
 class LoadMemberInstruction(Instruction):
 class LoadMemberInstruction(Instruction):
     """An instruction that produces a value by loading a member from a container."""
     """An instruction that produces a value by loading a member from a container."""
@@ -984,6 +1349,10 @@ class LoadMemberInstruction(Instruction):
         """Gets this instruction's sequence of child instructions."""
         """Gets this instruction's sequence of child instructions."""
         return [self.container]
         return [self.container]
 
 
+    def has_result_temporary(self):
+        """Tells if this instruction stores its result in a temporary."""
+        return False
+
     def create(self, new_children):
     def create(self, new_children):
         """Creates a new instruction of this type from the given sequence of child instructions."""
         """Creates a new instruction of this type from the given sequence of child instructions."""
         container, = new_children
         container, = new_children
@@ -1001,6 +1370,9 @@ class LoadMemberInstruction(Instruction):
             self.container.generate_python_use(code_generator),
             self.container.generate_python_use(code_generator),
             self.member_name)
             self.member_name)
 
 
+    def __repr__(self):
+        return 'LoadMemberInstruction(%r, %r)' % (self.container, self.member_name)
+
 class StoreMemberInstruction(VoidInstruction):
 class StoreMemberInstruction(VoidInstruction):
     """An instruction that stores a value in a container member."""
     """An instruction that stores a value in a container member."""
     def __init__(self, container, member_name, value):
     def __init__(self, container, member_name, value):
@@ -1032,6 +1404,9 @@ class StoreMemberInstruction(VoidInstruction):
             self.member_name,
             self.member_name,
             self.value.generate_python_use(code_generator)))
             self.value.generate_python_use(code_generator)))
 
 
+    def __repr__(self):
+        return 'StoreMemberInstruction(%r, %r, %r)' % (self.container, self.member_name, self.value)
+
 class NopInstruction(VoidInstruction):
 class NopInstruction(VoidInstruction):
     """A nop instruction, which allows for the kernel's thread of execution to be interrupted."""
     """A nop instruction, which allows for the kernel's thread of execution to be interrupted."""
     def generate_python_def(self, code_generator):
     def generate_python_def(self, code_generator):
@@ -1039,6 +1414,9 @@ class NopInstruction(VoidInstruction):
            The statement is appended immediately to the code generator."""
            The statement is appended immediately to the code generator."""
         code_generator.append_line('yield %s' % repr(NOP_LITERAL))
         code_generator.append_line('yield %s' % repr(NOP_LITERAL))
 
 
+    def __repr__(self):
+        return 'NopInstruction()'
+
 class ReadValueInstruction(StateInstruction):
 class ReadValueInstruction(StateInstruction):
     """An instruction that reads a value from a node."""
     """An instruction that reads a value from a node."""
     def __init__(self, node_id):
     def __init__(self, node_id):
@@ -1079,6 +1457,21 @@ class ReadDictionaryValueInstruction(StateInstruction):
         """Gets this state instruction's argument list."""
         """Gets this state instruction's argument list."""
         return [self.node_id, self.key]
         return [self.node_id, self.key]
 
 
+class ReadDictionaryNodeInstruction(StateInstruction):
+    """An instruction that reads a dictionary node."""
+    def __init__(self, node_id, key):
+        StateInstruction.__init__(self)
+        self.node_id = node_id
+        self.key = key
+
+    def get_opcode(self):
+        """Gets the opcode for this state instruction."""
+        return "RDN"
+
+    def get_arguments(self):
+        """Gets this state instruction's argument list."""
+        return [self.node_id, self.key]
+
 class ReadDictionaryEdgeInstruction(StateInstruction):
 class ReadDictionaryEdgeInstruction(StateInstruction):
     """An instruction that reads a dictionary edge."""
     """An instruction that reads a dictionary edge."""
     def __init__(self, node_id, key):
     def __init__(self, node_id, key):
@@ -1094,6 +1487,20 @@ class ReadDictionaryEdgeInstruction(StateInstruction):
         """Gets this state instruction's argument list."""
         """Gets this state instruction's argument list."""
         return [self.node_id, self.key]
         return [self.node_id, self.key]
 
 
+class ReadDictionaryKeysInstruction(StateInstruction):
+    """An instruction that reads all keys from a dictionary."""
+    def __init__(self, node_id):
+        StateInstruction.__init__(self)
+        self.node_id = node_id
+
+    def get_opcode(self):
+        """Gets the opcode for this state instruction."""
+        return "RDK"
+
+    def get_arguments(self):
+        """Gets this state instruction's argument list."""
+        return [self.node_id]
+
 class ReadEdgeInstruction(StateInstruction):
 class ReadEdgeInstruction(StateInstruction):
     """An instruction that reads an edge."""
     """An instruction that reads an edge."""
     def __init__(self, node_id):
     def __init__(self, node_id):
@@ -1247,7 +1654,9 @@ def create_block(*statements):
             statements[0],
             statements[0],
             create_block(*statements[1:]))
             create_block(*statements[1:]))
 
 
-def create_jit_call(target, named_arguments, kwargs):
+def create_jit_call(
+        target, named_arguments, kwargs,
+        create_run_generator=RunGeneratorFunctionInstruction):
     """Creates a call that abides by the JIT's calling convention."""
     """Creates a call that abides by the JIT's calling convention."""
     # A JIT call looks like this:
     # A JIT call looks like this:
     #
     #
@@ -1273,9 +1682,19 @@ def create_jit_call(target, named_arguments, kwargs):
             [kwargs]))
             [kwargs]))
     return CompoundInstruction(
     return CompoundInstruction(
         create_block(*results),
         create_block(*results),
-        RunGeneratorFunctionInstruction(
+        create_run_generator(
             target, arg_dict.create_load(), NODE_RESULT_TYPE))
             target, arg_dict.create_load(), NODE_RESULT_TYPE))
 
 
+def evaluate_and_load(value):
+    """Creates a statement that evaluates the given tree, and creates
+       an expression that loads the result. These instructions are returned
+       as a pair of trees."""
+    if isinstance(value, (LoadLocalInstruction, LiteralInstruction)):
+        return EmptyInstruction(), value
+    else:
+        store = StoreLocalInstruction(None, value)
+        return IgnoreInstruction(store), store.create_load()
+
 def create_new_local_node(local_variable, connected_node, edge_variable=None):
 def create_new_local_node(local_variable, connected_node, edge_variable=None):
     """Creates a local node that is the backing storage for a local variable.
     """Creates a local node that is the backing storage for a local variable.
        This node is connected to a given node to make sure it's not perceived
        This node is connected to a given node to make sure it's not perceived
@@ -1286,22 +1705,7 @@ def create_new_local_node(local_variable, connected_node, edge_variable=None):
     if edge_variable is not None:
     if edge_variable is not None:
         create_edge = StoreLocalInstruction(edge_variable, create_edge)
         create_edge = StoreLocalInstruction(edge_variable, create_edge)
 
 
-    return create_block(local_store, create_edge)
-
-def with_debug_info_trace(instruction, debug_info, function_name):
-    """Prepends the given instruction with a tracing instruction that prints
-       the given debug information and function name."""
-    if debug_info is None and function_name is None:
-        return instruction
-    else:
-        if debug_info is None:
-            debug_info = 'unknown location '
-        if function_name is None:
-            function_name = 'unknown function'
-        return create_block(
-            PrintInstruction(
-                LiteralInstruction('TRACE: %s(%s, JIT)' % (debug_info, function_name))),
-            instruction)
+    return create_block(IgnoreInstruction(local_store), IgnoreInstruction(create_edge))
 
 
 def map_instruction_tree_top_down(function, instruction):
 def map_instruction_tree_top_down(function, instruction):
     """Applies the given mapping function to every instruction in the tree
     """Applies the given mapping function to every instruction in the tree
@@ -1464,19 +1868,3 @@ def protect_temporaries_from_gc(instruction, connected_node):
             return instruction
             return instruction
 
 
     return map_instruction_tree_top_down(protect_result, instruction)
     return map_instruction_tree_top_down(protect_result, instruction)
-
-if __name__ == "__main__":
-    example_tree = SelectInstruction(
-        LiteralInstruction(True),
-        LoopInstruction(
-            CompoundInstruction(
-                BreakInstruction(),
-                CompoundInstruction(
-                    EmptyInstruction(),
-                    ContinueInstruction()
-                )
-            )
-        ),
-        ReturnInstruction(
-            EmptyInstruction()))
-    print(example_tree.simplify())

+ 18 - 14
kernel/modelverse_kernel/main.py

@@ -4,6 +4,7 @@ from modelverse_kernel.request_handler import RequestHandler
 import modelverse_jit.jit as jit
 import modelverse_jit.jit as jit
 import modelverse_jit.intrinsics as jit_intrinsics
 import modelverse_jit.intrinsics as jit_intrinsics
 import modelverse_jit.jit_primitives as jit_primitives
 import modelverse_jit.jit_primitives as jit_primitives
+import modelverse_jit.runtime as jit_runtime
 from collections import defaultdict
 from collections import defaultdict
 import sys
 import sys
 import time
 import time
@@ -31,10 +32,9 @@ class ModelverseKernel(object):
         self.allow_compiled = True
         self.allow_compiled = True
         #self.allow_compiled = False
         #self.allow_compiled = False
 
 
-        # suggested_function_names maps body ids to suggested function names.
-        # You can tell the kernel to stop caring about getting the function names
-        # right by setting this to `None`.
-        self.suggested_function_names = {}
+        # Set `self.suggest_function_names` to True to associate global function names
+        # with their function bodies.
+        self.suggest_function_names = True
 
 
         # `self.jit` handles most JIT-related functionality.
         # `self.jit` handles most JIT-related functionality.
         self.jit = jit.ModelverseJit()
         self.jit = jit.ModelverseJit()
@@ -52,11 +52,19 @@ class ModelverseKernel(object):
         #
         #
         #     self.jit.allow_direct_calls(False)
         #     self.jit.allow_direct_calls(False)
         #
         #
+        # To disable thunks in the JIT, uncomment the line below:
+        #
+        #     self.jit.enable_thunks(False)
+        #
         # To make the JIT compile 'input' instructions as calls to
         # To make the JIT compile 'input' instructions as calls to
         # modelverse_jit.runtime.get_input, uncomment the line below:
         # modelverse_jit.runtime.get_input, uncomment the line below:
         #
         #
         #     self.jit.use_input_function()
         #     self.jit.use_input_function()
         #
         #
+        # To disable source maps in the JIT, uncomment the line below:
+        #
+        #     self.jit.enable_source_maps(False)
+        #
         # To enable tracing in the JIT (for debugging purposes), uncomment
         # To enable tracing in the JIT (for debugging purposes), uncomment
         # the line below:
         # the line below:
         #
         #
@@ -182,11 +190,7 @@ class ModelverseKernel(object):
 
 
     def jit_compile(self, task_root, inst):
     def jit_compile(self, task_root, inst):
         # Try to retrieve the suggested name.
         # Try to retrieve the suggested name.
-        if self.suggested_function_names is not None and inst in self.suggested_function_names:
-            suggested_name = self.suggested_function_names[inst]
-        else:
-            suggested_name = None
-
+        suggested_name = self.jit.get_global_name(inst)
         # Have the JIT compile the function.
         # Have the JIT compile the function.
         return self.jit.jit_compile(task_root, inst, suggested_name)
         return self.jit.jit_compile(task_root, inst, suggested_name)
 
 
@@ -576,7 +580,7 @@ class ModelverseKernel(object):
                                    ("CNV", ["finish"]),
                                    ("CNV", ["finish"]),
                                   ]
                                   ]
             if variable is None:
             if variable is None:
-                raise Exception("Not found as global: %s" % var_name)
+                raise Exception(jit_runtime.GLOBAL_NOT_FOUND_MESSAGE_FORMAT % var_name)
 
 
             # Resolved a global, so this is a string
             # Resolved a global, so this is a string
             # Potentially, this might even be a function that we have precompiled already!
             # Potentially, this might even be a function that we have precompiled already!
@@ -587,18 +591,18 @@ class ModelverseKernel(object):
                     # We have a compiled function ready!
                     # We have a compiled function ready!
                     # Now we have to bind the ID to the compiled functions
                     # Now we have to bind the ID to the compiled functions
                     # For this, we read out the body of the resolved data
                     # For this, we read out the body of the resolved data
-                    compiler_val, =  yield [("RD", [variable, "value"])]
+                    compiler_val, = yield [("RD", [variable, "value"])]
                     compiler_body, = yield [("RD", [compiler_val, "body"])]
                     compiler_body, = yield [("RD", [compiler_val, "body"])]
                     self.jit.register_compiled(compiler_body, compiled_function, var_name)
                     self.jit.register_compiled(compiler_body, compiled_function, var_name)
 
 
             # If we're dealing with a function, then we might want to figure out what its body id
             # If we're dealing with a function, then we might want to figure out what its body id
             # is now so we can suggest a name to the JIT later.
             # is now so we can suggest a name to the JIT later.
-            if self.suggested_function_names is not None:
-                compiler_val, =  yield [("RD", [variable, "value"])]
+            if self.suggest_function_names and self.jit.get_global_body_id(var_name) is None:
+                compiler_val, = yield [("RD", [variable, "value"])]
                 if compiler_val is not None:
                 if compiler_val is not None:
                     compiler_body, = yield [("RD", [compiler_val, "body"])]
                     compiler_body, = yield [("RD", [compiler_val, "body"])]
                     if compiler_body is not None:
                     if compiler_body is not None:
-                        self.suggested_function_names[compiler_body] = var_name
+                        self.jit.register_global(compiler_body, var_name)
 
 
         else:
         else:
             phase_link, returnvalue_link, new_phase = \
             phase_link, returnvalue_link, new_phase = \

+ 131 - 42
kernel/modelverse_kernel/request_handler.py

@@ -1,20 +1,72 @@
+import sys
 import modelverse_kernel.primitives as primitive_functions
 import modelverse_kernel.primitives as primitive_functions
+import modelverse_jit.runtime as jit_runtime
 
 
 class KnownRequestHandled(Exception):
 class KnownRequestHandled(Exception):
     """An exception that signifies that a known request was handled."""
     """An exception that signifies that a known request was handled."""
     pass
     pass
 
 
+class GeneratorStackEntry(object):
+    """An entry in the generator stack of a request handles."""
+    def __init__(self, generator):
+        self.generator = generator
+        self.function_name = None
+        self.source_map = None
+        self.function_origin = None
+        self.pending_requests = None
+        self.finished_requests = True
+        self.replies = []
+        self.has_reply = False
+
+    def append_reply(self, new_reply):
+        """Appends a reply to the this entry's list of pending replies."""
+        self.replies.append(new_reply)
+        self.has_reply = True
+
+    def extend_replies(self, new_replies):
+        """Appends a list of replies to this entry's list of pending replies."""
+        if new_replies is not None:
+            self.replies.extend(new_replies)
+            self.has_reply = True
+
+    def step(self):
+        """Performs a single step: accumulated replies are fed to the generator,
+           which then produces requests."""
+        # Send the replies to the generator, and ask for new requests.
+        self.pending_requests = self.generator.send(self.replies if self.has_reply else None)
+
+        # Reset some data structures.
+        self.finished_requests = False
+        self.replies = []
+        self.has_reply = False
+
+def format_stack_trace(stack_trace):
+    """Formats a list of (function name, debug info, origin) triples."""
+    return '\n'.join([jit_runtime.format_stack_frame(*triple) for triple in stack_trace])
+
+class UnhandledRequestHandlerException(Exception):
+    """The type of exception that is thrown when the request handler encounters an
+       unhandled exception."""
+    def __init__(self, inner_exception, stack_trace):
+        Exception.__init__(
+            self,
+            """The request handler encountered an unknown exception.\n
+               Inner exception: %s\n
+               Stack trace:\n%s\n""" % (inner_exception, format_stack_trace(stack_trace)))
+        self.inner_exception = inner_exception
+        self.stack_trace = stack_trace
+
 class RequestHandler(object):
 class RequestHandler(object):
     """A type of object that intercepts logic-related Modelverse requests, and
     """A type of object that intercepts logic-related Modelverse requests, and
        forwards Modelverse state requests."""
        forwards Modelverse state requests."""
     def __init__(self):
     def __init__(self):
-        # generator_stack is a stack of (generator, pending requests, request replies, has-reply)
-        # tuples.
+        # generator_stack is a stack of GeneratorStackEntry values.
         self.generator_stack = []
         self.generator_stack = []
         # exception_handlers is a stack of
         # exception_handlers is a stack of
         # (generator_stack index, [(exception type, handler function)])
         # (generator_stack index, [(exception type, handler function)])
         # tuples.
         # tuples.
         self.exception_handlers = []
         self.exception_handlers = []
+        self.produce_stack_trace = True
         self.handlers = {
         self.handlers = {
             'CALL' : self.execute_call,
             'CALL' : self.execute_call,
             'CALL_ARGS' : self.execute_call_args,
             'CALL_ARGS' : self.execute_call_args,
@@ -24,7 +76,8 @@ class RequestHandler(object):
             'TAIL_CALL_KWARGS' : self.execute_tail_call_kwargs,
             'TAIL_CALL_KWARGS' : self.execute_tail_call_kwargs,
             'TRY' : self.execute_try,
             'TRY' : self.execute_try,
             'CATCH' : self.execute_catch,
             'CATCH' : self.execute_catch,
-            'END_TRY' : self.execute_end_try
+            'END_TRY' : self.execute_end_try,
+            'DEBUG_INFO' : self.execute_debug_info
         }
         }
 
 
     def is_active(self):
     def is_active(self):
@@ -45,14 +98,14 @@ class RequestHandler(object):
                 if self.has_pending_requests():
                 if self.has_pending_requests():
                     try:
                     try:
                         # Try to pop a request for the modelverse state.
                         # Try to pop a request for the modelverse state.
-                        result = self.pop_requests()
-                        return result
+                        return self.pop_requests()
                     except KnownRequestHandled:
                     except KnownRequestHandled:
                         # Carry on.
                         # Carry on.
                         pass
                         pass
 
 
-                # Perform a single generator step.
-                self.step()
+                if not self.has_pending_requests():
+                    # Perform a single generator step.
+                    self.step()
             except StopIteration:
             except StopIteration:
                 # Done, so remove the generator
                 # Done, so remove the generator
                 self.pop_generator()
                 self.pop_generator()
@@ -75,22 +128,24 @@ class RequestHandler(object):
                     return None
                     return None
             except Exception as ex:
             except Exception as ex:
                 # Maybe get an exception handler to do this.
                 # Maybe get an exception handler to do this.
-                if not self.handle_exception(ex):
-                    raise
+                stack_trace = self.handle_exception(ex)
+                if stack_trace is not None:
+                    if self.produce_stack_trace:
+                        raise UnhandledRequestHandlerException(ex, stack_trace)
+                    else:
+                        raise
 
 
     def set_finished_requests_flag(self):
     def set_finished_requests_flag(self):
         """Sets the finished_requests flag in the top-of-stack tuple."""
         """Sets the finished_requests flag in the top-of-stack tuple."""
-        current_generator, requests, _, replies, has_reply = self.generator_stack[-1]
-        self.generator_stack[-1] = (current_generator, requests, True, replies, has_reply)
+        self.generator_stack[-1].finished_requests = True
 
 
     def has_pending_requests(self):
     def has_pending_requests(self):
         """Tests if the top-of-stack generator has pending requests."""
         """Tests if the top-of-stack generator has pending requests."""
-        _, _, finished_requests, _, _ = self.generator_stack[-1]
-        return not finished_requests
+        return not self.generator_stack[-1].finished_requests
 
 
     def push_generator(self, gen):
     def push_generator(self, gen):
         """Pushes a new generator onto the stack."""
         """Pushes a new generator onto the stack."""
-        self.generator_stack.append((gen, None, True, [], False))
+        self.generator_stack.append(GeneratorStackEntry(gen))
         # print('Pushed generator %s. Generator count: %d' % (gen, len(self.generator_stack)))
         # print('Pushed generator %s. Generator count: %d' % (gen, len(self.generator_stack)))
 
 
     def pop_generator(self):
     def pop_generator(self):
@@ -113,30 +168,16 @@ class RequestHandler(object):
 
 
     def append_reply(self, new_reply):
     def append_reply(self, new_reply):
         """Appends a reply to the top-of-stack generator's list of pending replies."""
         """Appends a reply to the top-of-stack generator's list of pending replies."""
-        current_generator, requests, requests_done, replies, has_reply = self.generator_stack[-1]
-        replies.append(new_reply)
-        has_reply = True
-        self.generator_stack[-1] = (current_generator, requests, requests_done, replies, has_reply)
+        self.generator_stack[-1].append_reply(new_reply)
 
 
     def extend_replies(self, new_replies):
     def extend_replies(self, new_replies):
         """Appends a list of replies to the top-of-stack generator's list of pending replies."""
         """Appends a list of replies to the top-of-stack generator's list of pending replies."""
-        current_generator, requests, requests_done, replies, has_reply = self.generator_stack[-1]
-        if new_replies is not None:
-            replies.extend(new_replies)
-            has_reply = True
-            self.generator_stack[-1] = (
-                current_generator, requests, requests_done, replies, has_reply)
+        self.generator_stack[-1].extend_replies(new_replies)
 
 
     def step(self):
     def step(self):
         """Performs a single step: accumulated replies are fed to the generator,
         """Performs a single step: accumulated replies are fed to the generator,
            which then produces requests."""
            which then produces requests."""
-        current_generator, _, _, replies, has_reply = self.generator_stack[-1]
-
-        # Send the replies to the generator, and ask for new requests.
-        requests = current_generator.send(replies if has_reply else None)
-
-        # Update the entry on the stack.
-        self.generator_stack[-1] = (current_generator, requests, False, [], False)
+        self.generator_stack[-1].step()
 
 
     def handle_exception(self, exception):
     def handle_exception(self, exception):
         """Handles the given exception. A Boolean is returned that tells if
         """Handles the given exception. A Boolean is returned that tells if
@@ -158,20 +199,55 @@ class RequestHandler(object):
                 # a single 'TAIL_CALL_ARGS' request. The next iteration will replace
                 # a single 'TAIL_CALL_ARGS' request. The next iteration will replace
                 # the dummy frame by an actual frame.
                 # the dummy frame by an actual frame.
                 del self.generator_stack[stack_index:]
                 del self.generator_stack[stack_index:]
-                self.generator_stack.append(
-                    (None,
-                     [('TAIL_CALL_ARGS', [applicable_handler, (exception,)])],
-                     False,
-                     [],
-                     False))
-                return True
+                stack_entry = GeneratorStackEntry(None)
+                stack_entry.pending_requests = [
+                    ('TAIL_CALL_ARGS', [applicable_handler, (exception,)])]
+                stack_entry.finished_requests = False
+                self.generator_stack.append(stack_entry)
+                return None
 
 
         # We couldn't find an applicable exception handler, even after exhausting the
         # We couldn't find an applicable exception handler, even after exhausting the
         # entire exception handler stack. All is lost.
         # entire exception handler stack. All is lost.
-        # Also, clean up after ourselves.
-        self.generator_stack = []
+        # Also, clean up after ourselves by unwinding the stack.
+        return self.unwind_stack()
+
+    def unwind_stack(self):
+        """Unwinds the entirety of the stack. All generators and exception handlers are
+           discarded. A list of (function name, debug information, source) statements is
+           returned."""
+        class UnwindStackException(Exception):
+            """A hard-to-catch exception that is used to make generators crash.
+               The exceptions they produce can then be analyzed for line numbers."""
+            pass
+
+        # First throw away all exception handlers. We won't be needing them any more.
         self.exception_handlers = []
         self.exception_handlers = []
-        return False
+
+        # Then pop every generator from the stack and make it crash.
+        stack_trace = []
+        while len(self.generator_stack) > 0:
+            top_entry = self.generator_stack.pop()
+            if top_entry.function_origin is None:
+                # Skip this function.
+                continue
+
+            try:
+                # Crash the generator.
+                top_entry.generator.throw(UnwindStackException())
+            except UnwindStackException:
+                # Find out where the exception was thrown.
+                _, _, exc_traceback = sys.exc_info()
+                line_number = exc_traceback.tb_lineno
+                source_map = top_entry.source_map
+                if source_map is not None:
+                    debug_info = source_map.get_debug_info(line_number)
+                else:
+                    debug_info = None
+
+                function_name = top_entry.function_name
+                stack_trace.append((function_name, debug_info, top_entry.function_origin))
+
+        return stack_trace[::-1]
 
 
     def pop_requests(self):
     def pop_requests(self):
         """Tries to pop a batch of Modelverse _state_ requests from the
         """Tries to pop a batch of Modelverse _state_ requests from the
@@ -179,7 +255,7 @@ class RequestHandler(object):
 
 
            A list of requests and a Boolean are returned. The latter is True
            A list of requests and a Boolean are returned. The latter is True
            if there are no more requests to process, and false otherwise."""
            if there are no more requests to process, and false otherwise."""
-        _, requests, _, _, _ = self.generator_stack[-1]
+        requests = self.generator_stack[-1].pending_requests
         if requests is None or len(requests) == 0:
         if requests is None or len(requests) == 0:
             # Couldn't find a request for the state to handle.
             # Couldn't find a request for the state to handle.
             self.set_finished_requests_flag()
             self.set_finished_requests_flag()
@@ -285,6 +361,7 @@ class RequestHandler(object):
                 ("TRY was given argument list '%s', " +
                 ("TRY was given argument list '%s', " +
                  "expected exactly zero arguments.") % repr(request_args))
                  "expected exactly zero arguments.") % repr(request_args))
         self.exception_handlers.append((len(self.generator_stack) - 1, []))
         self.exception_handlers.append((len(self.generator_stack) - 1, []))
+        self.append_reply(None)
 
 
     def execute_catch(self, request_args):
     def execute_catch(self, request_args):
         """Executes a CATCH-request with the given argument list."""
         """Executes a CATCH-request with the given argument list."""
@@ -301,6 +378,7 @@ class RequestHandler(object):
                 'current generator.')
                 'current generator.')
 
 
         handlers.append((exception_type, handler))
         handlers.append((exception_type, handler))
+        self.append_reply(None)
 
 
     def execute_end_try(self, request_args):
     def execute_end_try(self, request_args):
         """Executes an END_TRY-request with the given argument list."""
         """Executes an END_TRY-request with the given argument list."""
@@ -325,4 +403,15 @@ class RequestHandler(object):
 
 
         # Everything seems to be in order. Pop the exception handler.
         # Everything seems to be in order. Pop the exception handler.
         self.exception_handlers.pop()
         self.exception_handlers.pop()
+        self.append_reply(None)
+
+    def execute_debug_info(self, request_args):
+        """Executes a DEBUG_INFO-request with the given argument list."""
+        # DEBUG_INFO updates the function name and source map for the top-of-stack generator.
+        # These two things allow us to unwind the stack neatly if an unhandled exception is
+        # encountered.
+        # Format: ("DEBUG_INFO", [function_name, source_map])
+        top_entry = self.generator_stack[-1]
+        top_entry.function_name, top_entry.source_map, top_entry.function_origin = request_args
+        top_entry.append_reply(None)
 
 

+ 47 - 0
performance/code/dict_iterate.alc

@@ -0,0 +1,47 @@
+include "primitives.alh"
+
+Void function log_dict_keys(dict : Element, n : Integer):
+	Element keys
+	Element k
+	Integer i
+
+	i = 0
+	keys = dict_keys(dict)
+	while (read_nr_out(keys) > 0):
+		k = set_pop(keys)
+		if (i == n):
+			log(k)
+			i = 0
+
+		i = i + 1
+
+	return!
+
+Element function create_dict(n : Integer):
+	Integer i
+	Element dict
+
+	i = 0
+	dict = create_node()
+
+	while (i < n):
+		dict_add(dict, i, 0)
+		i = i + 1
+
+	return dict!
+
+Void function test_main():
+	Integer size
+	Integer log_skip
+	Integer trip_count
+	Integer i
+
+	size = input()
+	log_skip = input()
+	trip_count = input()
+	i = 0
+	while (i < trip_count):
+		log_dict_keys(create_dict(size), log_skip)
+		i = i + 1
+
+	return!

+ 3 - 2
performance/perf2tex.py

@@ -11,7 +11,8 @@ LATEX_COLORS = [
     ('chartBlue', 0x4F81BD),
     ('chartBlue', 0x4F81BD),
     ('chartRed', 0xC0504D),
     ('chartRed', 0xC0504D),
     ('chartGreen', 0x9BBB59),
     ('chartGreen', 0x9BBB59),
-    ('chartPurple', 0x9F4C7C)
+    ('chartPurple', 0x9F4C7C),
+    ('chartDarkYellow', 0xCCCC00)
 ]
 ]
 
 
 LATEX_HEADER = r"""\documentclass[12pt,a4paper,onecolumn,openright]{report}
 LATEX_HEADER = r"""\documentclass[12pt,a4paper,onecolumn,openright]{report}
@@ -33,7 +34,7 @@ def encode_latex_string(value):
     """Encodes the given string as a LaTeX string."""
     """Encodes the given string as a LaTeX string."""
     # I guess this is good enough for now. This may need to be
     # I guess this is good enough for now. This may need to be
     # revisited if we encounter more complicated names.
     # revisited if we encounter more complicated names.
-    return value.replace('_', '\\_')
+    return '{%s}' % value.replace('_', '\\_')
 
 
 def assemble_latex_chart(optimization_levels, color_defs, test_names, data):
 def assemble_latex_chart(optimization_levels, color_defs, test_names, data):
     """Assembles a LaTeX chart from the given components."""
     """Assembles a LaTeX chart from the given components."""

+ 13 - 0
performance/test_dict_iterate.py

@@ -0,0 +1,13 @@
+import unittest
+import utils
+
+class TestDictIterate(unittest.TestCase):
+    def dict_iterate(self, optimization_level):
+        utils.write_perf_to_file(
+            'dict_iterate', optimization_level,
+            utils.run_perf_test(
+                ["test_harness.alc", "dict_iterate.alc", "primitives.alc", "jit.alc"],
+                [50, 10, 100],
+                optimization_level))
+
+utils.define_perf_tests(TestDictIterate, TestDictIterate.dict_iterate)

+ 11 - 1
performance/utils.py

@@ -28,10 +28,20 @@ PORTS = set()
 OPTIMIZATION_LEVEL_LEGACY_INTERPRETER = "legacy-interpreter"
 OPTIMIZATION_LEVEL_LEGACY_INTERPRETER = "legacy-interpreter"
 OPTIMIZATION_LEVEL_INTERPRETER = "interpreter"
 OPTIMIZATION_LEVEL_INTERPRETER = "interpreter"
 OPTIMIZATION_LEVEL_BASELINE_JIT = "baseline-jit"
 OPTIMIZATION_LEVEL_BASELINE_JIT = "baseline-jit"
+OPTIMIZATION_LEVEL_BASELINE_JIT_NO_THUNKS = "baseline-jit,no-thunks"
+OPTIMIZATION_LEVEL_FAST_JIT = "fast-jit"
+OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_LARGE_FUNCTIONS = "adaptive-jit-favor-large-functions"
+OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_SMALL_FUNCTIONS = "adaptive-jit-favor-small-functions"
+OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_LOOPS = "adaptive-jit-favor-loops"
 ALL_OPTIMIZATION_LEVELS = [
 ALL_OPTIMIZATION_LEVELS = [
     OPTIMIZATION_LEVEL_LEGACY_INTERPRETER,
     OPTIMIZATION_LEVEL_LEGACY_INTERPRETER,
     OPTIMIZATION_LEVEL_INTERPRETER,
     OPTIMIZATION_LEVEL_INTERPRETER,
-    OPTIMIZATION_LEVEL_BASELINE_JIT
+    OPTIMIZATION_LEVEL_BASELINE_JIT,
+    OPTIMIZATION_LEVEL_BASELINE_JIT_NO_THUNKS,
+    OPTIMIZATION_LEVEL_FAST_JIT,
+    OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_LARGE_FUNCTIONS,
+    OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_SMALL_FUNCTIONS,
+    OPTIMIZATION_LEVEL_ADAPTIVE_JIT_FAVOR_LOOPS
 ]
 ]
 
 
 class ModelverseTerminated(Exception):
 class ModelverseTerminated(Exception):

+ 4 - 2
scripts/run_local_modelverse.py

@@ -7,5 +7,7 @@ if len(sys.argv) < 2:
     sys.stderr.write("    %s port\n" % sys.argv[0])
     sys.stderr.write("    %s port\n" % sys.argv[0])
 else:
 else:
     subprocess.check_call([sys.executable, "-m", "sccd.compiler.sccdc", "-p", "threads", "server.xml"], cwd="hybrid_server")
     subprocess.check_call([sys.executable, "-m", "sccd.compiler.sccdc", "-p", "threads", "server.xml"], cwd="hybrid_server")
-    subprocess.call([sys.executable, "run_mvk_server.py"] + sys.argv[1:] + ["--kernel=baseline-jit"], cwd="hybrid_server")
-    #subprocess.call([sys.executable, "run_mvk_server.py"] + sys.argv[1:] + ["--kernel=legacy-interpreter"], cwd="hybrid_server")
+    # There's no need to specify `--kernel=baseline-jit` here, because that's the default kernel.
+    # Also, specifying a kernel here breaks the performance tests.
+    subprocess.call([sys.executable, "run_mvk_server.py"] + sys.argv[1:], cwd="hybrid_server")
+    # subprocess.call([sys.executable, "run_mvk_server.py"] + sys.argv[1:] + ["--kernel=legacy-interpreter"], cwd="hybrid_server")