Przeglądaj źródła

Create a bytecode IR parser

jonathanvdc 8 lat temu
rodzic
commit
92c74fe803
1 zmienionych plików z 116 dodań i 0 usunięć
  1. 116 0
      kernel/modelverse_jit/bytecode_parser.py

+ 116 - 0
kernel/modelverse_jit/bytecode_parser.py

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