|
@@ -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)
|