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