bytecode_parser.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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. raise primitive_functions.PrimitiveFinished(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_name, = yield [("RV", [node_id])]
  37. raise primitive_functions.PrimitiveFinished(
  38. bytecode_ir.VariableNode(node_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 result_type is bytecode_ir.VariableNode:
  43. yield [("TAIL_CALL_ARGS", [self.parse_variable, (node_id,)])]
  44. elif result_type is int:
  45. raise primitive_functions.PrimitiveFinished(node_id)
  46. else:
  47. yield [("TAIL_CALL_ARGS", [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, = yield [("CALL_ARGS", [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).__name__, result_type.__name__))
  55. raise primitive_functions.PrimitiveFinished(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 object.__new__(bytecode_ir.INSTRUCTION_TYPE_MAPPING[instruction_type])
  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. # print("Initializing '%s' node" % instr_type)
  89. if instr_type is bytecode_ir.CallInstruction:
  90. # Call instructions are complicated, so they get special treatment.
  91. yield [("TAIL_CALL_ARGS", [self.initialize_call, (instruction, node_id)])]
  92. else:
  93. # Construct an argument list based on the `constructor_parameters` attribute
  94. # of the instruction type.
  95. arg_list = []
  96. for dict_key, ctor_param_ty in instr_type.constructor_parameters:
  97. arg_id, = yield [("RD", [node_id, dict_key])]
  98. arg, = yield [("CALL_ARGS", [self.parse_node, (arg_id, ctor_param_ty)])]
  99. arg_list.append(arg)
  100. instruction.__init__(*arg_list)
  101. raise primitive_functions.PrimitiveFinished(None)