bytecode_parser.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. """Parses Modelverse bytecode graphs into bytecode ir."""
  2. import modelverse_jit.bytecode_ir as bytecode_ir
  3. import modelverse_jit.runtime as jit_runtime
  4. import modelverse_kernel.primitives as primitive_functions
  5. class BytecodeParser(object):
  6. """Parses bytecode graphs."""
  7. def __init__(self):
  8. self.parsed_nodes = {}
  9. def parse_instruction(self, node_id):
  10. """Parses the instruction node with the given identifier."""
  11. if node_id is None:
  12. return None
  13. elif node_id in self.parsed_nodes:
  14. # We've already parsed this node, so return it right away.
  15. raise primitive_functions.PrimitiveFinished(self.parsed_nodes[node_id])
  16. instruction_val, = yield [("RV", [node_id])]
  17. instruction_type = instruction_val["value"]
  18. # Create an instruction and store it in the instruction dictionary.
  19. instruction = self.create_instruction(instruction_type)
  20. self.parsed_nodes[node_id] = instruction
  21. # Initialize the instruction from the node's data.
  22. yield [("CALL_ARGS", [self.initialize_instruction, instruction, (node_id,)])]
  23. # Retrieve the debug information.
  24. debug_info, = yield [("RD", [node_id, "__debug"])]
  25. if debug_info is not None:
  26. debug_info, = yield [("RV", [debug_info])]
  27. instruction.debug_information = debug_info
  28. # Check if the instruction has a 'next' instruction.
  29. next_instr_id, = yield [("RD", [node_id, "next"])]
  30. if next_instr_id is not None:
  31. instruction.next_instruction, = yield [
  32. ("CALL_ARGS", [self.parse_instruction, (next_instr_id,)])]
  33. raise primitive_functions.PrimitiveFinished(instruction)
  34. def parse_variable(self, node_id):
  35. """Parses the given variable node."""
  36. var_id, = yield [("RD", [node_id, "var"])]
  37. var_name, = yield [("RV", [var_id])]
  38. return bytecode_ir.VariableNode(var_id, var_name)
  39. def __parse_node_unchecked(self, node_id, result_type):
  40. """Parses the given node as the specified type of object, without
  41. checking that the result actually conforms to said type."""
  42. if isinstance(result_type, bytecode_ir.VariableNode):
  43. return self.parse_variable(node_id)
  44. elif isinstance(result_type, int):
  45. return node_id
  46. else:
  47. return self.parse_instruction(node_id)
  48. def parse_node(self, node_id, result_type):
  49. """Parses the given node as the specified type of object."""
  50. result = self.__parse_node_unchecked(node_id, result_type)
  51. if result is not None and not isinstance(result, result_type):
  52. raise jit_runtime.JitCompilationFailedException(
  53. "Parsed a node as an instance of '%s', expected an instance of '%s'." % (
  54. type(result), result_type))
  55. return result
  56. def parse_arguments(self, first_argument_id):
  57. """Parses the parameter-to-argument mapping started by the specified first argument
  58. node."""
  59. next_param = first_argument_id
  60. named_args = []
  61. while next_param is not None:
  62. param_name_id, = yield [("RD", [next_param, "name"])]
  63. param_name, = yield [("RV", [param_name_id])]
  64. param_val_id, = yield [("RD", [next_param, "value"])]
  65. param_val, = yield [("CALL_ARGS", [self.parse_instruction, (param_val_id,)])]
  66. named_args.append((param_name, param_val))
  67. next_param, = yield [("RD", [next_param, "next_param"])]
  68. raise primitive_functions.PrimitiveFinished(named_args)
  69. def create_instruction(self, instruction_type):
  70. """Creates an instruction of the given type."""
  71. if instruction_type in bytecode_ir.INSTRUCTION_TYPE_MAPPING:
  72. return bytecode_ir.INSTRUCTION_TYPE_MAPPING[instruction_type].__new__()
  73. else:
  74. raise jit_runtime.JitCompilationFailedException(
  75. "Unknown instruction type: '%s'" % instruction_type)
  76. def initialize_call(self, call_instruction, node_id):
  77. """Initializes the given call instruction."""
  78. func_id, first_arg_id, = yield [
  79. ("RD", [node_id, "func"]),
  80. ("RD", [node_id, "params"])]
  81. func_val, = yield [("CALL_ARGS", [self.parse_instruction, (func_id,)])]
  82. named_args, = yield [("CALL_ARGS", [self.parse_arguments, (first_arg_id,)])]
  83. call_instruction.__init__(func_val, named_args)
  84. raise primitive_functions.PrimitiveFinished(None)
  85. def initialize_instruction(self, instruction, node_id):
  86. """Initializes the given instruction with data from the given node."""
  87. instr_type = type(instruction)
  88. if instr_type is bytecode_ir.CallInstruction:
  89. # Call instructions are complicated, so they get special treatment.
  90. yield [("TAIL_CALL_ARGS", [self.initialize_call, (instruction, node_id)])]
  91. else:
  92. # Construct an argument list based on the `constructor_parameters` attribute
  93. # of the instruction type.
  94. arg_list = []
  95. for dict_key, ctor_param_ty in instr_type.constructor_parameters:
  96. arg_id, = yield [("RD", [node_id, dict_key])]
  97. arg, = yield [("CALL_ARGS", [self.parse_node, (arg_id, ctor_param_ty)])]
  98. arg_list.append(arg)
  99. instruction.__init__(*arg_list)
  100. raise primitive_functions.PrimitiveFinished(None)