bytecode_to_cfg.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. """Converts bytecode IR to CFG IR."""
  2. import modelverse_jit.bytecode_ir as bytecode_ir
  3. import modelverse_jit.cfg_ir as cfg_ir
  4. import modelverse_jit.runtime as jit_runtime
  5. def emit_debug_info_trace(block, debug_info, function_name):
  6. """Appends a tracing instruction to the given block that prints
  7. the given debug information and function name."""
  8. if debug_info is not None or function_name is not None:
  9. block.append_definition(
  10. cfg_ir.create_print(
  11. block.append_definition(
  12. cfg_ir.Literal(jit_runtime.format_trace_message(
  13. debug_info, function_name, jit_runtime.FAST_JIT_ORIGIN_NAME)))))
  14. class AnalysisState(object):
  15. """State that is common to the bytecode->CFG transformation of a function."""
  16. def __init__(self, jit, function_name, param_dict):
  17. self.jit = jit
  18. self.function_name = function_name
  19. self.counter = cfg_ir.SharedCounter()
  20. self.analyzed_instructions = set()
  21. self.loop_instructions = {}
  22. self.entry_point = cfg_ir.BasicBlock(self.counter)
  23. self.current_block = self.entry_point
  24. self.root_node = None
  25. self.__write_prolog(param_dict)
  26. def __write_prolog(self, param_dict):
  27. # Write a prolog in CFG IR.
  28. # We want to create the following definition:
  29. #
  30. # !entry_point():
  31. # static if self.jit.source_maps_enabled:
  32. # $function_name = literal <function map>
  33. # $source_map_name = literal <source map name>
  34. # $origin_name = literal jit_runtime.FAST_JIT_ORIGIN_NAME
  35. # $_ = direct-call ('macro-positional', void) register_debug_info(
  36. # function_name=$functionsource_map=$source_map_name)
  37. #
  38. # $jit_locals = alloc-root-node
  39. # $_ = declare-local var(...)
  40. # $param_1 = resolve-local var(...)
  41. # $arg_1 = function-parameter ...
  42. # $_ = store $param_1, $arg_1
  43. # ...
  44. #
  45. # static if self.jit.nop_insertion_enabled:
  46. # $_ = direct-call ('macro-io', void) nop()
  47. #
  48. # We also want to store '$jit_locals' in an attribute, so we can
  49. # use it to shield locals from the GC.
  50. if self.jit.source_maps_enabled:
  51. function_name = self.current_block.append_definition(
  52. cfg_ir.Literal(self.function_name))
  53. source_map_name = self.current_block.append_definition(
  54. cfg_ir.Literal(self.jit.get_source_map_name(self.function_name)))
  55. origin_name = self.current_block.append_definition(
  56. cfg_ir.Literal(jit_runtime.FAST_JIT_ORIGIN_NAME))
  57. self.current_block.append_definition(
  58. cfg_ir.DirectFunctionCall(
  59. cfg_ir.REGISTER_DEBUG_INFO_MACRO_NAME,
  60. [('function_name', function_name),
  61. ('source_map', source_map_name),
  62. ('origin_name', origin_name)],
  63. calling_convention=cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION,
  64. has_value=False))
  65. self.root_node = self.current_block.append_definition(cfg_ir.AllocateRootNode())
  66. for node_id, name in param_dict.items():
  67. variable = bytecode_ir.VariableNode(node_id, name)
  68. self.current_block.append_definition(cfg_ir.DeclareLocal(variable, self.root_node))
  69. param_i = self.current_block.append_definition(cfg_ir.ResolveLocal(variable))
  70. arg_i = self.current_block.append_definition(cfg_ir.FunctionParameter(name))
  71. self.current_block.append_definition(cfg_ir.StoreAtPointer(param_i, arg_i))
  72. if self.jit.nop_insertion_enabled:
  73. self.current_block.append_definition(cfg_ir.create_nop())
  74. def analyze(self, instruction):
  75. """Analyzes the given instruction as a basic block."""
  76. if instruction in self.analyzed_instructions:
  77. raise jit_runtime.JitCompilationFailedException(
  78. 'Cannot jit non-tree instruction graph.')
  79. self.analyzed_instructions.add(instruction)
  80. # Find an analyzer.
  81. instruction_type = type(instruction)
  82. if instruction_type in self.instruction_analyzers:
  83. if self.jit.tracing_enabled:
  84. emit_debug_info_trace(
  85. self.current_block, instruction.debug_information, self.function_name)
  86. # Analyze the instruction.
  87. result = self.instruction_analyzers[instruction_type](self, instruction)
  88. if self.jit.source_maps_enabled:
  89. result.debug_information = instruction.debug_information
  90. # Check if the instruction has a 'next' instruction. If so, analyze it!
  91. if instruction.next_instruction is not None:
  92. next_result = self.analyze(instruction.next_instruction)
  93. if next_result.value.has_value() or (not result.value.has_value()):
  94. result = next_result
  95. return result
  96. else:
  97. raise jit_runtime.JitCompilationFailedException(
  98. "Unknown instruction type: '%s'" % type(instruction))
  99. def emit_select(self, create_condition, create_if_body, create_else_body):
  100. """Emits a 'select' instruction."""
  101. # Create blocks that look like this:
  102. #
  103. # !current_block(...):
  104. # ...
  105. # $cond = <condition>
  106. # select $cond, !if_block(), !else_block()
  107. #
  108. # !if_block():
  109. # $if_result = <if-body>
  110. # jump !phi_block($if_result)
  111. #
  112. # !else_block():
  113. # $else_result = <else-body>
  114. # jump !phi_block($else_result)
  115. #
  116. # !phi_block($result = block-parameter):
  117. # ...
  118. #
  119. if_block = cfg_ir.BasicBlock(self.counter)
  120. else_block = cfg_ir.BasicBlock(self.counter)
  121. phi_block = cfg_ir.BasicBlock(self.counter)
  122. param_def = phi_block.append_parameter(cfg_ir.BlockParameter())
  123. condition = create_condition()
  124. self.current_block.flow = cfg_ir.SelectFlow(
  125. condition, cfg_ir.Branch(if_block), cfg_ir.Branch(else_block))
  126. self.current_block = if_block
  127. if_result = create_if_body()
  128. self.current_block.flow = cfg_ir.create_jump(phi_block, [if_result])
  129. self.current_block = else_block
  130. else_result = create_else_body()
  131. self.current_block.flow = cfg_ir.create_jump(phi_block, [else_result])
  132. self.current_block = phi_block
  133. return param_def
  134. def analyze_if(self, instruction):
  135. """Analyzes an 'if' instruction."""
  136. def __analyze_condition():
  137. condition_node = self.analyze(instruction.condition)
  138. return self.current_block.append_definition(cfg_ir.Read(condition_node))
  139. return self.emit_select(
  140. __analyze_condition,
  141. lambda: self.analyze(instruction.if_clause),
  142. lambda:
  143. self.current_block.append_definition(cfg_ir.Literal(None))
  144. if instruction.else_clause is None
  145. else self.analyze(instruction.else_clause))
  146. def analyze_while(self, instruction):
  147. """Analyzes a 'while' instruction."""
  148. # Create blocks that look like this:
  149. #
  150. # !current_block(...):
  151. # ...
  152. # jump !loop_condition()
  153. #
  154. # !loop_condition():
  155. # $condition_node = <condition>
  156. # $condition = read condition_node
  157. # select $condition, !loop_body(), !loop_exit()
  158. #
  159. # !loop_body():
  160. # $result = <body>
  161. # static if jit.nop_insertion_enabled:
  162. # $_ = direct-call ('macro-io', void) nop()
  163. # jump !loop_condition()
  164. #
  165. # !loop_exit():
  166. # $nothing = literal None
  167. # ...
  168. loop_condition_block = cfg_ir.BasicBlock(self.counter)
  169. loop_body_block = cfg_ir.BasicBlock(self.counter)
  170. loop_exit_block = cfg_ir.BasicBlock(self.counter)
  171. self.loop_instructions[instruction] = (loop_condition_block, loop_exit_block)
  172. self.current_block.flow = cfg_ir.create_jump(loop_condition_block)
  173. self.current_block = loop_condition_block
  174. condition_node = self.analyze(instruction.condition)
  175. condition = self.current_block.append_definition(cfg_ir.Read(condition_node))
  176. self.current_block.flow = cfg_ir.SelectFlow(
  177. condition, cfg_ir.Branch(loop_body_block), cfg_ir.Branch(loop_exit_block))
  178. self.current_block = loop_body_block
  179. self.analyze(instruction.body)
  180. if self.jit.nop_insertion_enabled:
  181. self.current_block.append_definition(cfg_ir.create_nop())
  182. self.current_block.flow = cfg_ir.create_jump(loop_condition_block)
  183. self.current_block = loop_exit_block
  184. return loop_exit_block.append_definition(cfg_ir.Literal(None))
  185. def analyze_return(self, instruction):
  186. """Analyzes a 'return' instruction."""
  187. if instruction.value is None:
  188. return_value = self.current_block.append_definition(cfg_ir.Literal(None))
  189. else:
  190. return_value = self.analyze(instruction.value)
  191. # Don't forget to deallocate the root node.
  192. self.current_block.append_definition(cfg_ir.DeallocateRootNode(self.root_node))
  193. self.current_block.flow = cfg_ir.ReturnFlow(return_value)
  194. self.current_block = cfg_ir.BasicBlock(self.counter)
  195. return self.current_block.append_definition(cfg_ir.Literal(None))
  196. def analyze_constant(self, instruction):
  197. """Analyzes a 'constant' instruction."""
  198. return self.current_block.append_definition(cfg_ir.Literal(instruction.constant_id))
  199. def analyze_resolve(self, instruction):
  200. """Analyzes a 'resolve' instruction."""
  201. def __resolve_global_carefully():
  202. # We might be resolving a global that does not exist. In that case, we
  203. # need to throw. We want to create the following blocks:
  204. #
  205. # !current_block(...):
  206. # ...
  207. # $resolved_global = resolve-global global
  208. # $nothing = literal None
  209. # $condition = binary $resolved_global, 'is', $nothing
  210. # select $condition, !no_global_block(), !global_exists_block()
  211. #
  212. # !no_global_block():
  213. # $message = literal <GLOBAL_NOT_FOUND_MESSAGE_FORMAT % instruction.variable.name>
  214. # $exception = direct-call "simple-positional" Exception(message=$message)
  215. # throw $exception
  216. #
  217. # !global_exists_block():
  218. # ...
  219. #
  220. no_global_block = cfg_ir.BasicBlock(self.counter)
  221. global_exists_block = cfg_ir.BasicBlock(self.counter)
  222. resolved_global = self.current_block.append_definition(
  223. cfg_ir.ResolveGlobal(instruction.variable))
  224. nothing = self.current_block.append_definition(cfg_ir.Literal(None))
  225. condition = self.current_block.append_definition(
  226. cfg_ir.Binary(resolved_global, 'is', nothing))
  227. self.current_block.flow = cfg_ir.SelectFlow(
  228. condition, cfg_ir.Branch(no_global_block), cfg_ir.Branch(global_exists_block))
  229. message = no_global_block.append_definition(
  230. cfg_ir.Literal(
  231. jit_runtime.GLOBAL_NOT_FOUND_MESSAGE_FORMAT % instruction.variable.name))
  232. exception = no_global_block.append_definition(
  233. cfg_ir.DirectFunctionCall(
  234. 'Exception',
  235. [('message', message)],
  236. cfg_ir.SIMPLE_POSITIONAL_CALLING_CONVENTION))
  237. no_global_block.flow = cfg_ir.ThrowFlow(exception)
  238. self.current_block = global_exists_block
  239. return resolved_global
  240. return self.emit_select(
  241. lambda:
  242. self.current_block.append_definition(
  243. cfg_ir.CheckLocalExists(instruction.variable)),
  244. lambda:
  245. self.current_block.append_definition(
  246. cfg_ir.ResolveLocal(instruction.variable)),
  247. __resolve_global_carefully)
  248. def analyze_declare(self, instruction):
  249. """Analyzes a 'declare' instruction."""
  250. return self.current_block.append_definition(
  251. cfg_ir.DeclareLocal(instruction.variable, self.root_node))
  252. def analyze_global(self, instruction):
  253. """Analyzes a 'global' instruction."""
  254. resolved_global = self.current_block.append_definition(
  255. cfg_ir.ResolveGlobal(instruction.variable))
  256. nothing = self.current_block.append_definition(cfg_ir.Literal(None))
  257. return self.emit_select(
  258. lambda:
  259. self.current_block.append_definition(
  260. cfg_ir.Binary(resolved_global, 'is', nothing)),
  261. lambda:
  262. self.current_block.append_definition(
  263. cfg_ir.DeclareGlobal(instruction.variable)),
  264. lambda: resolved_global)
  265. def analyze_assign(self, instruction):
  266. """Analyzes an 'assign' instruction."""
  267. pointer_result = self.analyze(instruction.pointer)
  268. value_result = self.analyze(instruction.value)
  269. return self.current_block.append_definition(
  270. cfg_ir.StoreAtPointer(pointer_result, value_result))
  271. def analyze_access(self, instruction):
  272. """Analyzes an 'access' instruction."""
  273. pointer_result = self.analyze(instruction.pointer)
  274. return self.current_block.append_definition(cfg_ir.LoadPointer(pointer_result))
  275. def analyze_output(self, instruction):
  276. """Analyzes an 'output' instruction."""
  277. value_result = self.analyze(instruction.value)
  278. return self.current_block.append_definition(cfg_ir.create_output(value_result))
  279. def analyze_input(self, _):
  280. """Analyzes an 'input' instruction."""
  281. return self.current_block.append_definition(cfg_ir.create_input())
  282. def analyze_break(self, instruction):
  283. """Analyzes a 'break' instruction."""
  284. if instruction.loop not in self.loop_instructions:
  285. raise jit_runtime.JitCompilationFailedException(
  286. "'break' instruction targets a 'while' loop that has not been defined yet.")
  287. _, exit_block = self.loop_instructions[instruction.loop]
  288. self.current_block.flow = cfg_ir.create_jump(exit_block)
  289. self.current_block = cfg_ir.BasicBlock(self.counter)
  290. return self.current_block.append_definition(cfg_ir.Literal(None))
  291. def analyze_continue(self, instruction):
  292. """Analyzes a 'continue' instruction."""
  293. if instruction.loop not in self.loop_instructions:
  294. raise jit_runtime.JitCompilationFailedException(
  295. "'continue' instruction targets a 'while' loop that has not been defined yet.")
  296. if self.jit.nop_insertion_enabled:
  297. self.current_block.append_definition(cfg_ir.create_nop())
  298. cond_block, _ = self.loop_instructions[instruction.loop]
  299. self.current_block.flow = cfg_ir.create_jump(cond_block)
  300. self.current_block = cfg_ir.BasicBlock(self.counter)
  301. return self.current_block.append_definition(cfg_ir.Literal(None))
  302. def analyze_call(self, instruction):
  303. """Analyzes the given 'call' instruction."""
  304. target = self.analyze(instruction.target)
  305. arg_list = []
  306. for key, arg_instruction in instruction.argument_list:
  307. arg_list.append((key, self.analyze(arg_instruction)))
  308. return self.current_block.append_definition(cfg_ir.IndirectFunctionCall(target, arg_list))
  309. instruction_analyzers = {
  310. bytecode_ir.SelectInstruction : analyze_if,
  311. bytecode_ir.WhileInstruction : analyze_while,
  312. bytecode_ir.ReturnInstruction : analyze_return,
  313. bytecode_ir.ConstantInstruction : analyze_constant,
  314. bytecode_ir.ResolveInstruction : analyze_resolve,
  315. bytecode_ir.DeclareInstruction : analyze_declare,
  316. bytecode_ir.GlobalInstruction : analyze_global,
  317. bytecode_ir.AssignInstruction : analyze_assign,
  318. bytecode_ir.AccessInstruction : analyze_access,
  319. bytecode_ir.OutputInstruction : analyze_output,
  320. bytecode_ir.InputInstruction : analyze_input,
  321. bytecode_ir.CallInstruction : analyze_call,
  322. bytecode_ir.BreakInstruction : analyze_break,
  323. bytecode_ir.ContinueInstruction : analyze_continue
  324. }