Browse Source

Parse bytecode graphs before tree construction in the JIT

jonathanvdc 8 years ago
parent
commit
95525acbec

+ 655 - 0
kernel/modelverse_jit/bytecode_to_tree.py

@@ -0,0 +1,655 @@
+"""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))
+
+class AnalysisState(object):
+    """The state of a bytecode analysis call graph."""
+    def __init__(self, jit, body_id, user_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.user_root = user_root
+        self.jit = jit
+        self.local_mapping = local_mapping
+        self.function_name = jit.jitted_entry_points[body_id]
+        self.enclosing_loop_instruction = None
+
+    def get_local_name(self, local_id):
+        """Gets the name for a local with the given id."""
+        if local_id not in self.local_mapping:
+            self.local_mapping[local_id] = 'local%d' % local_id
+        return self.local_mapping[local_id]
+
+    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 retrieve_user_root(self):
+        """Creates an instruction that stores the user_root variable
+           in a local."""
+        return tree_ir.StoreLocalInstruction(
+            'user_root',
+            tree_ir.LoadIndexInstruction(
+                tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
+                tree_ir.LiteralInstruction('user_root')))
+
+    def load_kernel(self):
+        """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 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 self.jit.tracing_enabled and instruction.debug_information is not None:
+                outer_result = tree_ir.with_debug_info_trace(
+                    outer_result, instruction.debug_information, self.function_name)
+            # 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."""
+        # The plan is to basically generate this tree:
+        #
+        # value = <some tree>
+        # last_output, last_output_link, new_last_output = \
+        #                 yield [("RD", [user_root, "last_output"]),
+        #                        ("RDE", [user_root, "last_output"]),
+        #                        ("CN", []),
+        #                       ]
+        # _, _, _, _ = \
+        #                 yield [("CD", [last_output, "value", value]),
+        #                        ("CD", [last_output, "next", new_last_output]),
+        #                        ("CD", [user_root, "last_output", new_last_output]),
+        #                        ("DE", [last_output_link])
+        #                       ]
+        # yield None
+
+        value_val, = yield [("CALL_ARGS", [self.analyze, (instruction.value,)])]
+        value_local = tree_ir.StoreLocalInstruction('value', value_val)
+
+        store_user_root = self.retrieve_user_root()
+        last_output = tree_ir.StoreLocalInstruction(
+            'last_output',
+            tree_ir.ReadDictionaryValueInstruction(
+                store_user_root.create_load(),
+                tree_ir.LiteralInstruction('last_output')))
+
+        last_output_link = tree_ir.StoreLocalInstruction(
+            'last_output_link',
+            tree_ir.ReadDictionaryEdgeInstruction(
+                store_user_root.create_load(),
+                tree_ir.LiteralInstruction('last_output')))
+
+        new_last_output = tree_ir.StoreLocalInstruction(
+            'new_last_output',
+            tree_ir.CreateNodeInstruction())
+
+        result = tree_ir.create_block(
+            value_local,
+            store_user_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_user_root.create_load(),
+                tree_ir.LiteralInstruction('last_output'),
+                new_last_output.create_load()),
+            tree_ir.DeleteEdgeInstruction(last_output_link.create_load()),
+            tree_ir.NopInstruction())
+
+        raise primitive_functions.PrimitiveFinished(result)
+
+    def analyze_input(self, _):
+        """Tries to analyze the given 'input' instruction."""
+
+        # Possible alternative to the explicit syntax tree:
+        if self.jit.input_function_enabled:
+            raise primitive_functions.PrimitiveFinished(
+                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", [user_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", [user_root, "input", _next])]
+        #     yield [("CE", [jit_locals, value])]
+        #     yield [("DN", [_input])]
+
+        user_root = self.retrieve_user_root()
+        _input = tree_ir.StoreLocalInstruction(
+            None,
+            tree_ir.ReadDictionaryValueInstruction(
+                user_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(
+                    user_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(
+                                        self.load_kernel(),
+                                        'success',
+                                        tree_ir.LiteralInstruction(False)),
+                                    tree_ir.NopInstruction()),
+                                tree_ir.BreakInstruction()))),
+                    tree_ir.CreateDictionaryEdgeInstruction(
+                        user_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 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", [user_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.get_local_name(instruction.variable.node_id)
+
+        if instruction.variable.name is None:
+            raise primitive_functions.PrimitiveFinished(
+                tree_ir.LoadLocalInstruction(name))
+
+        user_root = self.retrieve_user_root()
+        global_var = tree_ir.StoreLocalInstruction(
+            'global_var',
+            tree_ir.ReadDictionaryValueInstruction(
+                tree_ir.ReadDictionaryValueInstruction(
+                    user_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(
+                        "Not found as global: %s" % 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(
+                        user_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.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 resolve a variable, we'll do something along the
+        # lines of:
+        #
+        #     _globals, = yield [("RD", [user_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
+
+        user_root = self.retrieve_user_root()
+        _globals = tree_ir.StoreLocalInstruction(
+            '_globals',
+            tree_ir.ReadDictionaryValueInstruction(
+                user_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(
+                    user_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],)])]
+
+        # Assignments work like this:
+        #
+        #     value_link = yield [("RDE", [variable, "value"])]
+        #     _, _ =       yield [("CD", [variable, "value", value]),
+        #                         ("DE", [value_link])]
+
+        variable = tree_ir.StoreLocalInstruction(None, var_r)
+        value = tree_ir.StoreLocalInstruction(None, value_r)
+        value_link = tree_ir.StoreLocalInstruction(
+            'value_link',
+            tree_ir.ReadDictionaryEdgeInstruction(
+                variable.create_load(),
+                tree_ir.LiteralInstruction('value')))
+
+        raise primitive_functions.PrimitiveFinished(
+            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 analyze_access(self, instruction):
+        """Tries to analyze the given 'access' instruction."""
+        var_r, = yield [("CALL_ARGS", [self.analyze, (instruction.pointer,)])]
+
+        # 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"])]
+
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.ReadDictionaryValueInstruction(
+                var_r,
+                tree_ir.LiteralInstruction('value')))
+
+    def analyze_direct_call(self, callee_id, callee_name, argument_list):
+        """Tries to analyze a direct 'call' instruction."""
+        self.register_function_var(callee_id)
+
+        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:
+            compiled_func = self.jit.lookup_compiled_function(callee_name)
+            if compiled_func is None:
+                # Compile the callee.
+                yield [
+                    ("CALL_ARGS", [self.jit.jit_compile, (self.user_root, body_id, callee_name)])]
+            else:
+                self.jit.register_compiled(body_id, compiled_func, 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,)])]
+
+        # 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 named_args])
+        raise primitive_functions.PrimitiveFinished(
+            tree_ir.create_jit_call(
+                tree_ir.LoadGlobalInstruction(jit_runtime.CALL_FUNCTION_NAME),
+                [('function_id', func_val), ('named_arguments', dict_literal)],
+                tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME)))
+
+    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):
+                resolved_var_name = target.pointer.variable.name
+
+                # Try to look up the name as a global.
+                _globals, = yield [("RD", [self.user_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
+    }

+ 5 - 4
kernel/modelverse_jit/intrinsics.py

@@ -1,6 +1,7 @@
-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.runtime as jit_runtime
 
 
 BINARY_INTRINSICS = {
 BINARY_INTRINSICS = {
     'value_eq' : '==',
     'value_eq' : '==',
@@ -239,14 +240,14 @@ MISC_INTRINSICS = {
     '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_userroot
     # read_userroot
     'read_userroot' :
     'read_userroot' :
         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('user_root')),
             tree_ir.LiteralInstruction('user_root')),
 
 
     # Dictionary operations
     # Dictionary operations

+ 25 - 715
kernel/modelverse_jit/jit.py

@@ -1,4 +1,7 @@
 import modelverse_kernel.primitives as primitive_functions
 import modelverse_kernel.primitives as primitive_functions
+import modelverse_jit.bytecode_ir as bytecode_ir
+import modelverse_jit.bytecode_parser as bytecode_parser
+import modelverse_jit.bytecode_to_tree as bytecode_to_tree
 import modelverse_jit.tree_ir as tree_ir
 import modelverse_jit.tree_ir as tree_ir
 import modelverse_jit.runtime as jit_runtime
 import modelverse_jit.runtime as jit_runtime
 import keyword
 import keyword
@@ -7,47 +10,6 @@ import keyword
 # in this module.
 # in this module.
 JitCompilationFailedException = jit_runtime.JitCompilationFailedException
 JitCompilationFailedException = jit_runtime.JitCompilationFailedException
 
 
-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."""
-
-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."""
-
-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 map_and_simplify_generator(function, instruction):
 def map_and_simplify_generator(function, instruction):
     """Applies the given mapping function to every instruction in the tree
     """Applies the given mapping function to every instruction in the tree
        that has the given instruction as root, and simplifies it on-the-fly.
        that has the given instruction as root, and simplifies it on-the-fly.
@@ -95,9 +57,10 @@ class ModelverseJit(object):
         self.jitted_parameters = {}
         self.jitted_parameters = {}
         self.jit_globals = {
         self.jit_globals = {
             'PrimitiveFinished' : primitive_functions.PrimitiveFinished,
             'PrimitiveFinished' : primitive_functions.PrimitiveFinished,
-            CALL_FUNCTION_NAME : jit_runtime.call_function,
-            GET_INPUT_FUNCTION_NAME : jit_runtime.get_input
+            jit_runtime.CALL_FUNCTION_NAME : jit_runtime.call_function,
+            jit_runtime.GET_INPUT_FUNCTION_NAME : jit_runtime.get_input
         }
         }
+        self.bytecode_graphs = {}
         self.jit_count = 0
         self.jit_count = 0
         self.max_instructions = max_instructions
         self.max_instructions = max_instructions
         self.compiled_function_lookup = compiled_function_lookup
         self.compiled_function_lookup = compiled_function_lookup
@@ -282,6 +245,16 @@ class ModelverseJit(object):
 
 
         raise primitive_functions.PrimitiveFinished(self.jitted_parameters[body_id])
         raise primitive_functions.PrimitiveFinished(self.jitted_parameters[body_id])
 
 
+    def jit_parse_bytecode(self, body_id):
+        """Parses the given function body as a bytecode graph."""
+        if body_id in self.bytecode_graphs:
+            raise primitive_functions.PrimitiveFinished(self.bytecode_graphs[body_id])
+
+        parser = bytecode_parser.BytecodeParser()
+        result, = yield [("CALL_ARGS", [parser.parse_instruction, (body_id,)])]
+        self.bytecode_graphs[body_id] = result
+        raise primitive_functions.PrimitiveFinished(result)
+
     def jit_compile(self, user_root, body_id, suggested_name=None):
     def jit_compile(self, user_root, body_id, suggested_name=None):
         """Tries to jit the function defined by the given entry point id and parameter list."""
         """Tries to jit the function defined by the given entry point id and parameter list."""
         # The comment below makes pylint shut up about our (hopefully benign) use of exec here.
         # The comment below makes pylint shut up about our (hopefully benign) use of exec here.
@@ -340,10 +313,11 @@ class ModelverseJit(object):
             # We can't just JIT mutable functions. That'd be dangerous.
             # We can't just JIT mutable functions. That'd be dangerous.
             raise JitCompilationFailedException(
             raise JitCompilationFailedException(
                 "Function was marked '%s'." % jit_runtime.MUTABLE_FUNCTION_KEY)
                 "Function was marked '%s'." % jit_runtime.MUTABLE_FUNCTION_KEY)
-        state = AnalysisState(
+        body_bytecode, = yield [("CALL_ARGS", [self.jit_parse_bytecode, (body_id,)])]
+        state = bytecode_to_tree.AnalysisState(
             self, body_id, user_root, body_param_dict,
             self, body_id, user_root, body_param_dict,
             self.max_instructions)
             self.max_instructions)
-        constructed_body, = yield [("CALL_ARGS", [state.analyze, (body_id,)])]
+        constructed_body, = yield [("CALL_ARGS", [state.analyze, (body_bytecode,)])]
         yield [("END_TRY", [])]
         yield [("END_TRY", [])]
         del self.compilation_dependencies[body_id]
         del self.compilation_dependencies[body_id]
 
 
@@ -352,15 +326,15 @@ class ModelverseJit(object):
         # Create a LOCALS_NODE_NAME node, and connect it to the user root.
         # Create a LOCALS_NODE_NAME node, and connect it to the user root.
         prologue_statements.append(
         prologue_statements.append(
             tree_ir.create_new_local_node(
             tree_ir.create_new_local_node(
-                LOCALS_NODE_NAME,
+                jit_runtime.LOCALS_NODE_NAME,
                 tree_ir.LoadIndexInstruction(
                 tree_ir.LoadIndexInstruction(
-                    tree_ir.LoadLocalInstruction(KWARGS_PARAMETER_NAME),
+                    tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
                     tree_ir.LiteralInstruction('user_root')),
                     tree_ir.LiteralInstruction('user_root')),
-                LOCALS_EDGE_NAME))
+                jit_runtime.LOCALS_EDGE_NAME))
         for (key, val) in param_dict.items():
         for (key, val) in param_dict.items():
             arg_ptr = tree_ir.create_new_local_node(
             arg_ptr = tree_ir.create_new_local_node(
                 body_param_dict[key],
                 body_param_dict[key],
-                tree_ir.LoadLocalInstruction(LOCALS_NODE_NAME))
+                tree_ir.LoadLocalInstruction(jit_runtime.LOCALS_NODE_NAME))
             prologue_statements.append(arg_ptr)
             prologue_statements.append(arg_ptr)
             prologue_statements.append(
             prologue_statements.append(
                 tree_ir.CreateDictionaryEdgeInstruction(
                 tree_ir.CreateDictionaryEdgeInstruction(
@@ -376,12 +350,12 @@ class ModelverseJit(object):
 
 
         # Shield temporaries from the GC.
         # Shield temporaries from the GC.
         constructed_body = tree_ir.protect_temporaries_from_gc(
         constructed_body = tree_ir.protect_temporaries_from_gc(
-            constructed_body, tree_ir.LoadLocalInstruction(LOCALS_NODE_NAME))
+            constructed_body, tree_ir.LoadLocalInstruction(jit_runtime.LOCALS_NODE_NAME))
 
 
         # Wrap the IR in a function definition, give it a unique name.
         # Wrap the IR in a function definition, give it a unique name.
         constructed_function = tree_ir.DefineFunctionInstruction(
         constructed_function = tree_ir.DefineFunctionInstruction(
             function_name,
             function_name,
-            parameter_list + ['**' + KWARGS_PARAMETER_NAME],
+            parameter_list + ['**' + jit_runtime.KWARGS_PARAMETER_NAME],
             constructed_body)
             constructed_body)
         # Convert the function definition to Python code, and compile it.
         # Convert the function definition to Python code, and compile it.
         exec(str(constructed_function), self.jit_globals)
         exec(str(constructed_function), self.jit_globals)
@@ -396,667 +370,3 @@ class ModelverseJit(object):
             self.jit_code_log_function(constructed_function)
             self.jit_code_log_function(constructed_function)
 
 
         raise primitive_functions.PrimitiveFinished(compiled_function)
         raise primitive_functions.PrimitiveFinished(compiled_function)
-
-class AnalysisState(object):
-    """The state of a bytecode analysis call graph."""
-    def __init__(self, jit, body_id, user_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.user_root = user_root
-        self.jit = jit
-        self.local_mapping = local_mapping
-        self.function_name = jit.jitted_entry_points[body_id]
-        self.enclosing_loop_instruction = None
-
-    def get_local_name(self, local_id):
-        """Gets the name for a local with the given id."""
-        if local_id not in self.local_mapping:
-            self.local_mapping[local_id] = 'local%d' % local_id
-        return self.local_mapping[local_id]
-
-    def register_local_var(self, local_id):
-        """Registers the given variable node id as a local."""
-        if local_id in self.function_vars:
-            raise 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 JitCompilationFailedException(
-                "Local is used as target of function call.")
-        self.function_vars.add(local_id)
-
-    def retrieve_user_root(self):
-        """Creates an instruction that stores the user_root variable
-           in a local."""
-        return tree_ir.StoreLocalInstruction(
-            'user_root',
-            tree_ir.LoadIndexInstruction(
-                tree_ir.LoadLocalInstruction(KWARGS_PARAMETER_NAME),
-                tree_ir.LiteralInstruction('user_root')))
-
-    def load_kernel(self):
-        """Creates an instruction that loads the Modelverse kernel."""
-        return tree_ir.LoadIndexInstruction(
-            tree_ir.LoadLocalInstruction(KWARGS_PARAMETER_NAME),
-            tree_ir.LiteralInstruction('mvk'))
-
-    def analyze(self, instruction_id):
-        """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_id in self.analyzed_instructions:
-            raise JitCompilationFailedException('Cannot jit non-tree instruction graph.')
-        elif (self.max_instructions is not None and
-              len(self.analyzed_instructions) > self.max_instructions):
-            raise JitCompilationFailedException('Maximum number of instructions exceeded.')
-
-        self.analyzed_instructions.add(instruction_id)
-        instruction_val, = yield [("RV", [instruction_id])]
-        instruction_val = instruction_val["value"]
-        if instruction_val in self.instruction_analyzers:
-            # If tracing is enabled, then this would be an appropriate time to
-            # retrieve the debug information.
-            if self.jit.tracing_enabled:
-                debug_info, = yield [("RD", [instruction_id, "__debug"])]
-                if debug_info is not None:
-                    debug_info, = yield [("RV", [debug_info])]
-
-            # Analyze the instruction itself.
-            outer_result, = yield [
-                ("CALL_ARGS", [self.instruction_analyzers[instruction_val], (self, instruction_id)])]
-            if self.jit.tracing_enabled:
-                outer_result = tree_ir.with_debug_info_trace(outer_result, debug_info, self.function_name)
-            # Check if the instruction has a 'next' instruction.
-            next_instr, = yield [("RD", [instruction_id, "next"])]
-            if next_instr is None:
-                raise primitive_functions.PrimitiveFinished(outer_result)
-            else:
-                next_result, = yield [("CALL_ARGS", [self.analyze, (next_instr,)])]
-                raise primitive_functions.PrimitiveFinished(
-                    tree_ir.CompoundInstruction(
-                        outer_result,
-                        next_result))
-        else:
-            raise JitCompilationFailedException(
-                "Unknown instruction type: '%s'" % (instruction_val))
-
-    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_id):
-        """Tries to analyze the given 'return' instruction."""
-        retval_id, = yield [("RD", [instruction_id, 'value'])]
-        def create_return(return_value):
-            return tree_ir.ReturnInstruction(
-                tree_ir.CompoundInstruction(
-                    return_value,
-                    tree_ir.DeleteEdgeInstruction(
-                        tree_ir.LoadLocalInstruction(LOCALS_EDGE_NAME))))
-
-        if retval_id is None:
-            raise primitive_functions.PrimitiveFinished(
-                create_return(
-                    tree_ir.EmptyInstruction()))
-        else:
-            retval, = yield [("CALL_ARGS", [self.analyze, (retval_id,)])]
-            raise primitive_functions.PrimitiveFinished(
-                create_return(retval))
-
-    def analyze_if(self, instruction_id):
-        """Tries to analyze the given 'if' instruction."""
-        cond, true, false = yield [
-            ("RD", [instruction_id, "cond"]),
-            ("RD", [instruction_id, "then"]),
-            ("RD", [instruction_id, "else"])]
-
-        analysis_results, = yield [("CALL_ARGS", [self.analyze_all, (
-            [cond, true]
-            if false is None
-            else [cond, true, false],)])]
-
-        if false is None:
-            cond_r, true_r = analysis_results
-            false_r = tree_ir.EmptyInstruction()
-        else:
-            cond_r, true_r, false_r = analysis_results
-
-        raise primitive_functions.PrimitiveFinished(
-            tree_ir.SelectInstruction(
-                tree_ir.ReadValueInstruction(cond_r),
-                true_r,
-                false_r))
-
-    def analyze_while(self, instruction_id):
-        """Tries to analyze the given 'while' instruction."""
-        cond, body = yield [
-            ("RD", [instruction_id, "cond"]),
-            ("RD", [instruction_id, "body"])]
-
-        # Analyze the condition.
-        cond_r, = yield [("CALL_ARGS", [self.analyze, (cond,)])]
-        # 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_id
-        body_r, = yield [("CALL_ARGS", [self.analyze, (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_id):
-        """Tries to analyze the given 'constant' (literal) instruction."""
-        node_id, = yield [("RD", [instruction_id, "node"])]
-        raise primitive_functions.PrimitiveFinished(
-            tree_ir.LiteralInstruction(node_id))
-
-    def analyze_output(self, instruction_id):
-        """Tries to analyze the given 'output' instruction."""
-        # The plan is to basically generate this tree:
-        #
-        # value = <some tree>
-        # last_output, last_output_link, new_last_output = \
-        #                 yield [("RD", [user_root, "last_output"]),
-        #                        ("RDE", [user_root, "last_output"]),
-        #                        ("CN", []),
-        #                       ]
-        # _, _, _, _ = \
-        #                 yield [("CD", [last_output, "value", value]),
-        #                        ("CD", [last_output, "next", new_last_output]),
-        #                        ("CD", [user_root, "last_output", new_last_output]),
-        #                        ("DE", [last_output_link])
-        #                       ]
-        # yield None
-
-        value_id, = yield [("RD", [instruction_id, "value"])]
-        value_val, = yield [("CALL_ARGS", [self.analyze, (value_id,)])]
-        value_local = tree_ir.StoreLocalInstruction('value', value_val)
-
-        store_user_root = self.retrieve_user_root()
-        last_output = tree_ir.StoreLocalInstruction(
-            'last_output',
-            tree_ir.ReadDictionaryValueInstruction(
-                store_user_root.create_load(),
-                tree_ir.LiteralInstruction('last_output')))
-
-        last_output_link = tree_ir.StoreLocalInstruction(
-            'last_output_link',
-            tree_ir.ReadDictionaryEdgeInstruction(
-                store_user_root.create_load(),
-                tree_ir.LiteralInstruction('last_output')))
-
-        new_last_output = tree_ir.StoreLocalInstruction(
-            'new_last_output',
-            tree_ir.CreateNodeInstruction())
-
-        result = tree_ir.create_block(
-            value_local,
-            store_user_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_user_root.create_load(),
-                tree_ir.LiteralInstruction('last_output'),
-                new_last_output.create_load()),
-            tree_ir.DeleteEdgeInstruction(last_output_link.create_load()),
-            tree_ir.NopInstruction())
-
-        raise primitive_functions.PrimitiveFinished(result)
-
-    def analyze_input(self, _):
-        """Tries to analyze the given 'input' instruction."""
-
-        # Possible alternative to the explicit syntax tree:
-        if self.jit.input_function_enabled:
-            raise primitive_functions.PrimitiveFinished(
-                tree_ir.create_jit_call(
-                    tree_ir.LoadGlobalInstruction(GET_INPUT_FUNCTION_NAME),
-                    [],
-                    tree_ir.LoadLocalInstruction(KWARGS_PARAMETER_NAME)))
-
-        # The plan is to generate this tree:
-        #
-        #     value = None
-        #     while True:
-        #         _input = yield [("RD", [user_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", [user_root, "input", _next])]
-        #     yield [("CE", [jit_locals, value])]
-        #     yield [("DN", [_input])]
-
-        user_root = self.retrieve_user_root()
-        _input = tree_ir.StoreLocalInstruction(
-            None,
-            tree_ir.ReadDictionaryValueInstruction(
-                user_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(
-                    user_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(
-                                        self.load_kernel(),
-                                        'success',
-                                        tree_ir.LiteralInstruction(False)),
-                                    tree_ir.NopInstruction()),
-                                tree_ir.BreakInstruction()))),
-                    tree_ir.CreateDictionaryEdgeInstruction(
-                        user_root.create_load(),
-                        tree_ir.LiteralInstruction('input'),
-                        tree_ir.ReadDictionaryValueInstruction(
-                            _input.create_load(),
-                            tree_ir.LiteralInstruction('next'))),
-                    tree_ir.CreateEdgeInstruction(
-                        tree_ir.LoadLocalInstruction(LOCALS_NODE_NAME),
-                        value.create_load()),
-                    tree_ir.DeleteNodeInstruction(_input.create_load())),
-                value.create_load()))
-
-    def analyze_resolve(self, instruction_id):
-        """Tries to analyze the given 'resolve' instruction."""
-        var_id, = yield [("RD", [instruction_id, "var"])]
-        var_name, = yield [("RV", [var_id])]
-
-        # To resolve a variable, we'll do something along the
-        # lines of:
-        #
-        #     if 'local_var' in locals():
-        #         tmp = local_var
-        #     else:
-        #         _globals, = yield [("RD", [user_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.get_local_name(var_id)
-
-        if var_name is None:
-            raise primitive_functions.PrimitiveFinished(
-                tree_ir.LoadLocalInstruction(name))
-
-        user_root = self.retrieve_user_root()
-        global_var = tree_ir.StoreLocalInstruction(
-            'global_var',
-            tree_ir.ReadDictionaryValueInstruction(
-                tree_ir.ReadDictionaryValueInstruction(
-                    user_root.create_load(),
-                    tree_ir.LiteralInstruction('globals')),
-                tree_ir.LiteralInstruction(var_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(
-                        "Not found as global: %s" % var_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(
-                        user_root,
-                        global_var,
-                        err_block),
-                    global_var.create_load())))
-
-    def analyze_declare(self, instruction_id):
-        """Tries to analyze the given 'declare' function."""
-        var_id, = yield [("RD", [instruction_id, "var"])]
-
-        self.register_local_var(var_id)
-
-        name = self.get_local_name(var_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(LOCALS_NODE_NAME))))
-
-    def analyze_global(self, instruction_id):
-        """Tries to analyze the given 'global' (declaration) instruction."""
-        var_id, = yield [("RD", [instruction_id, "var"])]
-        var_name, = yield [("RV", [var_id])]
-
-        # To resolve a variable, we'll do something along the
-        # lines of:
-        #
-        #     _globals, = yield [("RD", [user_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
-
-        user_root = self.retrieve_user_root()
-        _globals = tree_ir.StoreLocalInstruction(
-            '_globals',
-            tree_ir.ReadDictionaryValueInstruction(
-                user_root.create_load(),
-                tree_ir.LiteralInstruction('globals')))
-
-        global_var = tree_ir.StoreLocalInstruction(
-            'global_var',
-            tree_ir.ReadDictionaryValueInstruction(
-                _globals.create_load(),
-                tree_ir.LiteralInstruction(var_name)))
-
-        raise primitive_functions.PrimitiveFinished(
-            tree_ir.CompoundInstruction(
-                tree_ir.create_block(
-                    user_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(var_name),
-                                global_var.create_load())),
-                        tree_ir.EmptyInstruction())),
-                global_var.create_load()))
-
-    def analyze_assign(self, instruction_id):
-        """Tries to analyze the given 'assign' instruction."""
-        var_id, value_id = yield [("RD", [instruction_id, "var"]),
-                                  ("RD", [instruction_id, "value"])]
-
-        (var_r, value_r), = yield [("CALL_ARGS", [self.analyze_all, ([var_id, value_id],)])]
-
-        # Assignments work like this:
-        #
-        #     value_link = yield [("RDE", [variable, "value"])]
-        #     _, _ =       yield [("CD", [variable, "value", value]),
-        #                         ("DE", [value_link])]
-
-        variable = tree_ir.StoreLocalInstruction(None, var_r)
-        value = tree_ir.StoreLocalInstruction(None, value_r)
-        value_link = tree_ir.StoreLocalInstruction(
-            'value_link',
-            tree_ir.ReadDictionaryEdgeInstruction(
-                variable.create_load(),
-                tree_ir.LiteralInstruction('value')))
-
-        raise primitive_functions.PrimitiveFinished(
-            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 analyze_access(self, instruction_id):
-        """Tries to analyze the given 'access' instruction."""
-        var_id, = yield [("RD", [instruction_id, "var"])]
-        var_r, = yield [("CALL_ARGS", [self.analyze, (var_id,)])]
-
-        # 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"])]
-
-        raise primitive_functions.PrimitiveFinished(
-            tree_ir.ReadDictionaryValueInstruction(
-                var_r,
-                tree_ir.LiteralInstruction('value')))
-
-    def analyze_direct_call(self, callee_id, callee_name, first_parameter_id):
-        """Tries to analyze a direct 'call' instruction."""
-        self.register_function_var(callee_id)
-
-        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:
-            compiled_func = self.jit.lookup_compiled_function(callee_name)
-            if compiled_func is None:
-                # Compile the callee.
-                yield [("CALL_ARGS", [self.jit.jit_compile, (self.user_root, body_id, callee_name)])]
-            else:
-                self.jit.register_compiled(body_id, compiled_func, 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, (first_parameter_id,)])]
-
-        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(KWARGS_PARAMETER_NAME)))
-
-    def analyze_arguments(self, first_argument_id):
-        """Analyzes 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.analyze, (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 analyze_indirect_call(self, func_id, first_arg_id):
-        """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, (func_id,)])]
-        named_args, = yield [("CALL_ARGS", [self.analyze_arguments, (first_arg_id,)])]
-
-        # 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 named_args])
-        raise primitive_functions.PrimitiveFinished(
-            tree_ir.create_jit_call(
-                tree_ir.LoadGlobalInstruction(CALL_FUNCTION_NAME),
-                [('function_id', func_val), ('named_arguments', dict_literal)],
-                tree_ir.LoadLocalInstruction(KWARGS_PARAMETER_NAME)))
-
-    def try_analyze_direct_call(self, func_id, first_param_id):
-        """Tries to analyze the given 'call' instruction as a direct call."""
-        if not self.jit.direct_calls_allowed:
-            raise JitCompilationFailedException('Direct calls are not allowed by the JIT.')
-
-        # Figure out what the 'func' instruction's type is.
-        func_instruction_op, = yield [("RV", [func_id])]
-        if func_instruction_op['value'] == 'access':
-            # 'access(resolve(var))' instructions are translated to direct calls.
-            access_value_id, = yield [("RD", [func_id, "var"])]
-            access_value_op, = yield [("RV", [access_value_id])]
-            if access_value_op['value'] == 'resolve':
-                resolved_var_id, = yield [("RD", [access_value_id, "var"])]
-                resolved_var_name, = yield [("RV", [resolved_var_id])]
-
-                # Try to look up the name as a global.
-                _globals, = yield [("RD", [self.user_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, first_param_id)])]
-                    raise primitive_functions.PrimitiveFinished(result)
-        elif func_instruction_op['value'] == 'constant':
-            # 'const(func_id)' instructions are also translated to direct calls.
-            function_val_id, = yield [("RD", [func_id, "node"])]
-            result, = yield [("CALL_ARGS", [self.analyze_direct_call, (
-                function_val_id, None, first_param_id)])]
-            raise primitive_functions.PrimitiveFinished(result)
-
-        raise JitCompilationFailedException(
-            "Cannot JIT function calls that target an unknown value as direct calls.")
-
-    def analyze_call(self, instruction_id):
-        """Tries to analyze the given 'call' instruction."""
-        func_id, first_param_id, = yield [("RD", [instruction_id, "func"]),
-                                          ("RD", [instruction_id, "params"])]
-
-        def handle_exception(exception):
-            # Looks like we'll have to compile it as an indirect call.
-            gen = self.analyze_indirect_call(func_id, first_param_id)
-            result, = yield [("CALL", [gen])]
-            raise primitive_functions.PrimitiveFinished(result)
-
-        # Try to analyze the call as a direct call.
-        yield [("TRY", [])]
-        yield [("CATCH", [JitCompilationFailedException, handle_exception])]
-        result, = yield [("CALL_ARGS", [self.try_analyze_direct_call, (func_id, first_param_id)])]
-        yield [("END_TRY", [])]
-        raise primitive_functions.PrimitiveFinished(result)
-
-    def analyze_break(self, instruction_id):
-        """Tries to analyze the given 'break' instruction."""
-        target_instruction_id, = yield [("RD", [instruction_id, "while"])]
-        if target_instruction_id == self.enclosing_loop_instruction:
-            raise primitive_functions.PrimitiveFinished(tree_ir.BreakInstruction())
-        else:
-            raise JitCompilationFailedException(
-                "Multilevel 'break' is not supported by the baseline JIT.")
-
-    def analyze_continue(self, instruction_id):
-        """Tries to analyze the given 'continue' instruction."""
-        target_instruction_id, = yield [("RD", [instruction_id, "while"])]
-        if target_instruction_id == self.enclosing_loop_instruction:
-            raise primitive_functions.PrimitiveFinished(tree_ir.ContinueInstruction())
-        else:
-            raise JitCompilationFailedException(
-                "Multilevel 'continue' is not supported by the baseline JIT.")
-
-    instruction_analyzers = {
-        'if' : analyze_if,
-        'while' : analyze_while,
-        'return' : analyze_return,
-        'constant' : analyze_constant,
-        'resolve' : analyze_resolve,
-        'declare' : analyze_declare,
-        'global' : analyze_global,
-        'assign' : analyze_assign,
-        'access' : analyze_access,
-        'output' : analyze_output,
-        'input' : analyze_input,
-        'call' : analyze_call,
-        'break' : analyze_break,
-        'continue' : analyze_continue
-    }
-