Browse Source

Parse bytecode graphs before tree construction in the JIT

jonathanvdc 8 years ago
parent
commit
32ea339924

+ 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 modelverse_jit.jit as jit
+import modelverse_jit.tree_ir as tree_ir
+import modelverse_jit.runtime as jit_runtime
 
 BINARY_INTRINSICS = {
     'value_eq' : '==',
@@ -239,14 +240,14 @@ MISC_INTRINSICS = {
     'read_root' :
         lambda:
         tree_ir.LoadIndexInstruction(
-            tree_ir.LoadLocalInstruction(jit.KWARGS_PARAMETER_NAME),
+            tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
             tree_ir.LiteralInstruction('root')),
 
     # read_userroot
     'read_userroot' :
         lambda:
         tree_ir.LoadIndexInstruction(
-            tree_ir.LoadLocalInstruction(jit.KWARGS_PARAMETER_NAME),
+            tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
             tree_ir.LiteralInstruction('user_root')),
 
     # Dictionary operations

+ 25 - 715
kernel/modelverse_jit/jit.py

@@ -1,4 +1,7 @@
 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.runtime as jit_runtime
 import keyword
@@ -7,47 +10,6 @@ import keyword
 # in this module.
 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):
     """Applies the given mapping function to every instruction in the tree
        that has the given instruction as root, and simplifies it on-the-fly.
@@ -95,9 +57,10 @@ class ModelverseJit(object):
         self.jitted_parameters = {}
         self.jit_globals = {
             '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.max_instructions = max_instructions
         self.compiled_function_lookup = compiled_function_lookup
@@ -282,6 +245,16 @@ class ModelverseJit(object):
 
         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):
         """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.
@@ -340,10 +313,11 @@ class ModelverseJit(object):
             # We can't just JIT mutable functions. That'd be dangerous.
             raise JitCompilationFailedException(
                 "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.max_instructions)
-        constructed_body, = yield [("CALL_ARGS", [state.analyze, (body_id,)])]
+        constructed_body, = yield [("CALL_ARGS", [state.analyze, (body_bytecode,)])]
         yield [("END_TRY", [])]
         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.
         prologue_statements.append(
             tree_ir.create_new_local_node(
-                LOCALS_NODE_NAME,
+                jit_runtime.LOCALS_NODE_NAME,
                 tree_ir.LoadIndexInstruction(
-                    tree_ir.LoadLocalInstruction(KWARGS_PARAMETER_NAME),
+                    tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
                     tree_ir.LiteralInstruction('user_root')),
-                LOCALS_EDGE_NAME))
+                jit_runtime.LOCALS_EDGE_NAME))
         for (key, val) in param_dict.items():
             arg_ptr = tree_ir.create_new_local_node(
                 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(
                 tree_ir.CreateDictionaryEdgeInstruction(
@@ -376,12 +350,12 @@ class ModelverseJit(object):
 
         # Shield temporaries from the 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.
         constructed_function = tree_ir.DefineFunctionInstruction(
             function_name,
-            parameter_list + ['**' + KWARGS_PARAMETER_NAME],
+            parameter_list + ['**' + jit_runtime.KWARGS_PARAMETER_NAME],
             constructed_body)
         # Convert the function definition to Python code, and compile it.
         exec(str(constructed_function), self.jit_globals)
@@ -396,667 +370,3 @@ class ModelverseJit(object):
             self.jit_code_log_function(constructed_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
-    }
-