cfg_to_tree.py 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  1. """Lowers CFG-IR to tree-IR."""
  2. import modelverse_jit.cfg_ir as cfg_ir
  3. import modelverse_jit.tree_ir as tree_ir
  4. import modelverse_jit.runtime as jit_runtime
  5. import modelverse_jit.bytecode_to_tree as bytecode_to_tree
  6. # The CFG deconstruction code here is based on the relooper algorithm
  7. # as detailed in https://github.com/kripken/Relooper/blob/master/paper.pdf
  8. class FlowGraphComponent(object):
  9. """Represents a control-flow graph component."""
  10. def __init__(self, entry_blocks, blocks, reachable):
  11. self.entry_blocks = entry_blocks
  12. self.blocks = blocks
  13. self.reachable = reachable
  14. def get_entry_reachable_blocks(self):
  15. """Gets the set of all blocks that are reachable from the entry points."""
  16. return set.union(*[self.get_reachable_blocks(block) for block in self.entry_blocks])
  17. def get_reachable_blocks(self, block):
  18. """Gets all blocks in this component that are reachable from the given block."""
  19. return set.intersection(self.reachable[block], self.blocks)
  20. def get_directly_reachable_blocks(self, block):
  21. """Gets all blocks in this component that are reachable from the given block by taking
  22. exactly one branch."""
  23. return set.intersection(cfg_ir.get_directly_reachable_blocks(block), self.blocks)
  24. def can_reach(self, source_block, target_block):
  25. """Tests if the first block can reach the second."""
  26. return target_block in self.reachable[source_block] and target_block in self.blocks
  27. def sub_component(self, new_entry_points):
  28. """Creates a sub-component of this component, with the given new entry points."""
  29. result = FlowGraphComponent(new_entry_points, self.blocks, self.reachable)
  30. result.blocks = result.get_entry_reachable_blocks()
  31. return result
  32. def create_graph_component(entry_point):
  33. """Creates a flow graph component from the given entry point."""
  34. reachable = cfg_ir.get_all_reachable_blocks(entry_point)
  35. all_blocks = set(reachable[entry_point])
  36. all_blocks.add(entry_point)
  37. return FlowGraphComponent([entry_point], all_blocks, reachable)
  38. class SimpleBlock(object):
  39. """A 'simple' block in the relooper algorithm."""
  40. def __init__(self, body, next_block):
  41. self.body = body
  42. self.next_block = next_block
  43. def lower(self, state):
  44. """Lowers this 'simple' block to a tree."""
  45. return tree_ir.create_block(
  46. state.lower_block(self.body),
  47. self.next_block.lower(state))
  48. class EmptyBlock(object):
  49. """An empty relooper block."""
  50. def lower(self, _):
  51. """Lowers this empty block to a tree."""
  52. return tree_ir.EmptyInstruction()
  53. class LoopBlock(object):
  54. """A 'loop' block in the relooper algorithm."""
  55. def __init__(self, inner, next_block):
  56. self.inner = inner
  57. self.next_block = next_block
  58. def lower(self, state):
  59. """Lowers this 'loop' block to a tree."""
  60. return tree_ir.create_block(
  61. tree_ir.LoopInstruction(self.inner.lower(state)),
  62. self.next_block.lower(state))
  63. class MultipleBlock(object):
  64. """A 'multiple' block in the relooper algorithm that does _not_ require a loop."""
  65. def __init__(self, handled_blocks, next_block):
  66. self.handled_blocks = handled_blocks
  67. self.next_block = next_block
  68. def lower_handled_blocks(self, state):
  69. """Lowers the handled blocks of this 'multiple' block to a tree."""
  70. result = tree_ir.EmptyInstruction()
  71. for entry, block in self.handled_blocks:
  72. result = tree_ir.SelectInstruction(
  73. tree_ir.BinaryInstruction(
  74. tree_ir.LoadLocalInstruction(state.label_variable),
  75. '==',
  76. tree_ir.LiteralInstruction(entry.index)),
  77. block.lower(state),
  78. result)
  79. return result
  80. def lower(self, state):
  81. """Lowers this 'multiple' block to a tree."""
  82. return tree_ir.create_block(
  83. self.lower_handled_blocks(state),
  84. self.next_block.lower(state))
  85. class MultipleLoopBlock(MultipleBlock):
  86. """A 'multiple' block in the relooper algorithm."""
  87. def __init__(self, handled_blocks, next_block):
  88. MultipleBlock.__init__(self, handled_blocks, next_block)
  89. def lower(self, state):
  90. """Lowers this 'multiple' block to a tree."""
  91. return tree_ir.create_block(
  92. tree_ir.LoopInstruction(self.lower_handled_blocks(state)),
  93. self.next_block.lower(state))
  94. class ContinueFlow(cfg_ir.FlowInstruction):
  95. """Represents a control flow instruction which continues to the next loop iteration."""
  96. def __init__(self, loop):
  97. cfg_ir.FlowInstruction.__init__(self)
  98. self.loop = loop
  99. def get_dependencies(self):
  100. """Gets all definitions and instructions on which this instruction depends."""
  101. return []
  102. def branches(self):
  103. """Gets a list of basic blocks targeted by this flow instruction."""
  104. return []
  105. def __str__(self):
  106. return 'continue'
  107. class BreakFlow(cfg_ir.FlowInstruction):
  108. """Represents a control flow instruction which breaks out of a loop."""
  109. def __init__(self, loop):
  110. cfg_ir.FlowInstruction.__init__(self)
  111. self.loop = loop
  112. def get_dependencies(self):
  113. """Gets all definitions and instructions on which this instruction depends."""
  114. return []
  115. def branches(self):
  116. """Gets a list of basic blocks targeted by this flow instruction."""
  117. return []
  118. def __str__(self):
  119. return 'break'
  120. def solipsize(graph_component, loop, entry_blocks, next_blocks):
  121. """Replaces branches to entry blocks and next blocks by jumps to 'continue' and 'break'
  122. blocks, respectively."""
  123. all_blocks = set()
  124. reachable_from_inner = set()
  125. for block in graph_component.blocks:
  126. if block in next_blocks:
  127. continue
  128. all_blocks.add(block)
  129. for branch in block.flow.branches():
  130. if branch.block in entry_blocks:
  131. # Create a 'continue' block, and point to that.
  132. continue_block = cfg_ir.BasicBlock(block.counter)
  133. for param in branch.block.parameters:
  134. continue_block.append_parameter(param)
  135. continue_block.flow = ContinueFlow(loop)
  136. branch.block = continue_block
  137. all_blocks.add(continue_block)
  138. elif branch.block in next_blocks:
  139. # Create a 'break' block, and point to that.
  140. break_block = cfg_ir.BasicBlock(block.counter)
  141. for param in branch.block.parameters:
  142. break_block.append_parameter(param)
  143. break_block.flow = BreakFlow(loop)
  144. branch.block = break_block
  145. all_blocks.add(break_block)
  146. reachable_from_inner.add(branch.block)
  147. return reachable_from_inner, FlowGraphComponent(
  148. graph_component.entry_blocks,
  149. all_blocks,
  150. {
  151. block : cfg_ir.get_reachable_blocks(block)
  152. for block in all_blocks
  153. })
  154. def to_relooper_loop(graph_component):
  155. """Converts the given graph component to a relooper 'loop'."""
  156. entry_blocks = graph_component.entry_blocks
  157. result = LoopBlock(None, None)
  158. inner_blocks = []
  159. next_blocks = []
  160. for block in graph_component.blocks:
  161. if any([graph_component.can_reach(block, ep) for ep in entry_blocks]):
  162. inner_blocks.append(block)
  163. else:
  164. next_blocks.append(block)
  165. reachable_from_inner, inner_component = solipsize(
  166. graph_component, result, entry_blocks, next_blocks)
  167. result.inner = reloop(inner_component)
  168. next_component = FlowGraphComponent(
  169. reachable_from_inner, next_blocks, graph_component.reachable)
  170. result.next_block = reloop(next_component)
  171. return result
  172. def to_relooper_multiple_or_loop(graph_component):
  173. """Converts the given graph component to a relooper 'multiple' or 'loop'."""
  174. # From the Emscripten paper:
  175. #
  176. # If we have more than one entry, try to create a Multiple block:
  177. # For each entry, find all the labels it reaches that
  178. # cannot be reached by any other entry. If at least one entry
  179. # has such labels, return a Multiple block, whose Handled
  180. # blocks are blocks for those labels (and whose entries are
  181. # those labels), and whose Next block is all the rest. Entries
  182. # for the next block are entries that did not become part of
  183. # the Handled blocks, and also labels that can be reached
  184. # from the Handled blocks.
  185. entry_blocks = graph_component.entry_blocks
  186. if len(entry_blocks) <= 1:
  187. return to_relooper_loop(graph_component)
  188. entry_reachables = {ep : graph_component.get_reachable_blocks(ep) for ep in entry_blocks}
  189. exclusive_entries = {}
  190. for entry in entry_blocks:
  191. exclusive_blocks = set(entry_reachables[entry])
  192. for other_entry in entry_blocks:
  193. if other_entry != entry:
  194. exclusive_blocks.difference_update(entry_reachables[other_entry])
  195. if len(exclusive_blocks) > 0:
  196. exclusive_entries[entry] = exclusive_blocks
  197. if len(exclusive_entries) == 0:
  198. return to_relooper_loop(graph_component)
  199. next_entries = set(graph_component.entry_blocks)
  200. for block_set in exclusive_entries.values():
  201. for elem in block_set:
  202. directly_reachable = graph_component.get_directly_reachable_blocks(elem)
  203. directly_reachable.remove(elem)
  204. next_entries.update(directly_reachable)
  205. next_entries.difference_update(exclusive_entries.keys())
  206. result = MultipleLoopBlock({}, None)
  207. for entry, exclusive_blocks in exclusive_entries.items():
  208. other_blocks = set(graph_component.blocks)
  209. other_blocks.difference_update(exclusive_blocks)
  210. result.handled_blocks[entry] = reloop(
  211. solipsize(graph_component, result, set([entry]), other_blocks))
  212. result.next_block = reloop(graph_component.sub_component(next_entries))
  213. return result
  214. def reloop(graph_component):
  215. """Applies the relooper algorithm to the given graph component."""
  216. entry_blocks = graph_component.entry_blocks
  217. if len(entry_blocks) == 0:
  218. return EmptyBlock()
  219. reachable_set = graph_component.get_entry_reachable_blocks()
  220. if len(entry_blocks) == 1 and entry_blocks[0] not in reachable_set:
  221. graph_component.blocks.remove(entry_blocks[0])
  222. return SimpleBlock(
  223. entry_blocks[0],
  224. reloop(
  225. FlowGraphComponent(
  226. graph_component.get_directly_reachable_blocks(entry_blocks[0]),
  227. graph_component.blocks,
  228. graph_component.reachable)))
  229. elif all([block in reachable_set for block in entry_blocks]):
  230. return to_relooper_loop(graph_component)
  231. else:
  232. return to_relooper_multiple_or_loop(graph_component)
  233. def reloop_trivial(graph_component):
  234. """Converts the given control-flow graph to a 'multiple' block that contains only 'simple'
  235. blocks."""
  236. return MultipleLoopBlock(
  237. [(block, SimpleBlock(block, EmptyBlock())) for block in graph_component.blocks],
  238. EmptyBlock())
  239. def reloop_function_body(entry_point):
  240. """Reloops the control-flow graph defined by the given entry point."""
  241. return reloop_trivial(create_graph_component(entry_point))
  242. def find_inlinable_definitions(entry_point):
  243. """Computes a set of definitions which are eligible for inlining, i.e., they
  244. are used only once and are consumed in the same basic block in which they
  245. are defined."""
  246. def_users, def_blocks = cfg_ir.find_all_def_uses(entry_point)
  247. # Find all definitions which are eligible for inlining.
  248. eligible_defs = set()
  249. for definition, users in def_users.items():
  250. if len(users) == 1:
  251. single_user = next(iter(users))
  252. if def_blocks[single_user] == def_blocks[definition]:
  253. eligible_defs.add(definition)
  254. return eligible_defs
  255. def lower_gc_protect(protected_value, root):
  256. """Lowers a GC_PROTECT_MACRO_NAME macro call."""
  257. return tree_ir.IgnoreInstruction(tree_ir.CreateEdgeInstruction(root, protected_value))
  258. def lower_maybe_gc_protect(condition, protected_value, root):
  259. """Lowers a MAYBE_GC_PROTECT_MACRO_NAME macro call."""
  260. return tree_ir.SelectInstruction(
  261. tree_ir.BinaryInstruction(condition, 'is not', tree_ir.LiteralInstruction(None)),
  262. lower_gc_protect(protected_value, root),
  263. tree_ir.EmptyInstruction())
  264. def lower_register_debug_info(function_name, source_map, origin):
  265. """Lowers a REGISTER_DEBUG_INFO_MACRO_NAME call."""
  266. assert isinstance(source_map, tree_ir.LiteralInstruction)
  267. return tree_ir.RegisterDebugInfoInstruction(
  268. function_name, tree_ir.LoadGlobalInstruction(source_map.literal), origin)
  269. class SimpleDefinitionScheduler(object):
  270. """Schedules definitions within a basic block in the order they occur in."""
  271. def __init__(self, lowering_state, definitions, flow):
  272. self.lowering_state = lowering_state
  273. self.definition_queue = definitions + [flow]
  274. self.definition_set = set(self.definition_queue)
  275. def use(self, definition):
  276. """Creates a tree that produces the given definition's value."""
  277. def_value = cfg_ir.get_def_value(definition)
  278. if isinstance(def_value, LoweringState.inline_value_types):
  279. return self.lowering_state.lower_value(def_value)
  280. elif isinstance(def_value, cfg_ir.AllocateRootNode):
  281. return tree_ir.LoadLocalInstruction(
  282. self.lowering_state.get_root_node_name(def_value))
  283. elif definition.has_value():
  284. return self.lowering_state.create_definition_load(definition)
  285. else:
  286. return tree_ir.EmptyInstruction()
  287. def get_schedulable_definition(self):
  288. """Finds a schedulable definition. Returns None if there are no definitions left to
  289. schedule."""
  290. if len(self.definition_queue) == 0:
  291. return None
  292. else:
  293. return self.definition_queue[-1]
  294. def __schedule_single_def(self, definition, store_and_forget, statements):
  295. if isinstance(definition, cfg_ir.FlowInstruction):
  296. statements.append(self.lowering_state.lower_value(definition))
  297. else:
  298. # We're sure that we're dealing with a bona fide cfg_ir.Definition now.
  299. definition_value = definition.value
  300. if cfg_ir.is_value_def(definition_value, LoweringState.inline_value_types):
  301. pass
  302. else:
  303. lowered_value = self.lowering_state.lower_value(definition_value)
  304. if (definition.debug_information is not None
  305. and self.lowering_state.jit.source_maps_enabled):
  306. lowered_value = tree_ir.DebugInfoInstruction(
  307. lowered_value, definition.debug_information)
  308. if definition.has_value():
  309. def_load = self.lowering_state.create_definition_load(definition)
  310. statements.append(
  311. tree_ir.IgnoreInstruction(def_load.create_store(lowered_value)))
  312. if not store_and_forget:
  313. statements.append(def_load)
  314. else:
  315. statements.append(lowered_value)
  316. def has_scheduled(self, definition):
  317. """Tests if the given definition has already been scheduled."""
  318. return definition not in self.definition_set
  319. def schedule(self, definition, store_and_forget=False):
  320. """Schedules the given definition. The resulting tree is returned."""
  321. assert definition in self.definition_set
  322. index = self.definition_queue.index(definition)
  323. defs_to_schedule = self.definition_queue[:index + 1]
  324. del self.definition_queue[:index + 1]
  325. statements = []
  326. for schedule_def in defs_to_schedule:
  327. self.definition_set.remove(schedule_def)
  328. self.__schedule_single_def(schedule_def, store_and_forget, statements)
  329. return tree_ir.create_block(*statements)
  330. class DependencyDefinitionScheduler(object):
  331. """Schedules definitions within a basic block in an order that tries to maximize
  332. the inlining of expressions."""
  333. # We now have the opportunity to schedule definitions in a somewhat intelligent way.
  334. # We ought to make sure that we don't reorder operations in an observable way, though.
  335. # Specifically:
  336. #
  337. # We are allowed to reorder operations which have no side-effects,
  338. # as long as they do not depend on each other. So this is fine:
  339. #
  340. # $a = load $x
  341. # $b = load $y
  342. # $c = call-indirect 'jit' f($b, $a)
  343. #
  344. # ==>
  345. #
  346. # $c = call-indirect 'jit' f(load $y, load $x)
  347. #
  348. # But we are not allowed to reorder operations which do have side-effects.
  349. # This is _not_ okay:
  350. #
  351. # $a = load $x
  352. # $b = call-indirect 'jit' g()
  353. # $c = store $x, $a
  354. #
  355. # ==>
  356. #
  357. # $b = call-indirect 'jit' g()
  358. # $c = store $x, load $x
  359. #
  360. # We can combine the concepts of depending on the results of other instructions
  361. # and depending on the potential side-effects of instructions by extending the notion
  362. # of what a dependency is: every instruction depends on its explicit dependencies,
  363. # and has an implicit dependency on all effectful instructions that precede it.
  364. # Additionally, every instruction with side-effects also depends on every instruction
  365. # that precedes it, regardless of whether those instructions have side-effects or not.
  366. #
  367. # The advantage of this way of looking at things is that we can now construct a dependency
  368. # graph. For example, this is the dependency graph for the second example.
  369. #
  370. # $c = store $x, $a
  371. # / \
  372. # / \
  373. # v v
  374. # $a = load $x <------------- $b = call-indirect 'jit' g()
  375. #
  376. # Any topological sort of the dependency graph is a valid instruction scheduling, but we
  377. # specifically want to grow a tree that uses few temporaries.
  378. #
  379. # We can accomplish this by growing the tree in reverse: we pick a definition on which no other
  380. # definition depends, and remove it from the dependency graph. We then iterate over the
  381. # definition's dependencies in reverse. If a dependency is encountered that is eligible for
  382. # inlining, i.e., it is used only once and is defined in the basic block where it is used,
  383. # then we really want to schedule it inline -- doing so allows us to avoid defining a temporary.
  384. # Here's the algorithm that we'll use to schedule dependencies.
  385. #
  386. # while (dependency has not been scheduled) and (another definition depends on the dependency):
  387. # pick a definition that depends on the dependency and schedule it
  388. # store the scheduled definition's value in a temporary
  389. #
  390. # if (dependency has not been scheduled) and (dependency is eligible for inlining):
  391. # schedule the dependency inline
  392. # else if (dependency has not been scheduled):
  393. # schedule the dependency, and store it in a temporary
  394. # else:
  395. # load the (already scheduled) dependency's value from a temporary
  396. #
  397. # Note that it is possible for another definition to also depend on a dependency, despite having
  398. # the dependency used only once. This merely means that at least one dependency is based on an
  399. # instruction's side-effects.
  400. #
  401. # Okay, so that's the theory. In this class, we will use the following data structure to
  402. # improve performance:
  403. #
  404. # * We will implement our graph as a map of definitions to a
  405. # (outgoing edges, incoming edges) tuple. This will allow us to quickly test if a
  406. # definition is a dependency of some other definition, and will also allow us to
  407. # erase definitions from the graph efficiently.
  408. #
  409. # * We will not create implicit (side-effect--based) dependencies beyond the last
  410. # definition with side-effects. Said definition already depends on every definition
  411. # that precedes it, so those edges are simply redundant.
  412. #
  413. def __init__(self, lowering_state, definitions, flow):
  414. self.lowering_state = lowering_state
  415. self.dependency_graph = {}
  416. self.construct_dependency_graph(definitions + [flow])
  417. def add_dependency(self, definition, dependency):
  418. """Makes the given definition dependent on the given dependency in the
  419. dependency graph."""
  420. def_outgoing_edges, _ = self.dependency_graph[definition]
  421. _, dep_incoming_edges = self.dependency_graph[dependency]
  422. def_outgoing_edges.add(dependency)
  423. dep_incoming_edges.add(definition)
  424. def construct_dependency_graph(self, definitions):
  425. """Construct the dependency graph for the given set of definitions."""
  426. def_set = set(definitions)
  427. # Initialize the dependency graph for every definition in the basic block.
  428. for definition in definitions:
  429. self.dependency_graph[definition] = (set(), set())
  430. # Construct the dependency graph.
  431. last_def_batch = []
  432. last_effectful_def = None
  433. for definition in definitions:
  434. # Explicit dependencies.
  435. for dep in definition.get_all_dependencies():
  436. if dep in def_set:
  437. self.add_dependency(definition, dep)
  438. # Implicit dependency on the previous definition with side-effects.
  439. if last_effectful_def is not None:
  440. self.add_dependency(definition, last_effectful_def)
  441. # Implicit dependency of definitions with side-effects on all definitions
  442. # that precede them. Implementation detail: we don't actually make
  443. # definitions dependent on _all_ definitions that precede them. It suffices
  444. # to insert a dependency on every definition between the current definition
  445. # and the previous definition with side-effects.
  446. if definition.has_side_effects():
  447. for implicit_dependency in last_def_batch:
  448. self.add_dependency(definition, implicit_dependency)
  449. last_def_batch = []
  450. last_effectful_def = definition
  451. else:
  452. last_def_batch.append(definition)
  453. def remove_definition(self, definition):
  454. """Removes the given definition from the graph."""
  455. assert not self.has_dependents(definition)
  456. # Remove the definition from the graph.
  457. outgoing_edges, _ = self.dependency_graph[definition]
  458. del self.dependency_graph[definition]
  459. # Remove the definition's outgoing edges from the graph.
  460. for dependency in outgoing_edges:
  461. _, incoming_edges = self.dependency_graph[dependency]
  462. incoming_edges.remove(definition)
  463. def has_scheduled(self, definition):
  464. """Tests if the given definition has already been scheduled."""
  465. return definition not in self.dependency_graph
  466. def get_schedulable_definition(self):
  467. """Finds a schedulable definition. Returns None if the dependency graph is empty."""
  468. if len(self.dependency_graph) == 0:
  469. return None
  470. for node in self.dependency_graph.keys():
  471. if not self.has_dependents(node):
  472. return node
  473. raise ValueError(
  474. 'Could not find an instruction to schedule. Dependency graph: %s' %
  475. self.dependency_graph)
  476. def has_dependents(self, definition):
  477. """Tests if there is at least one definition in the dependency graph that
  478. depends on the given definition."""
  479. return len(self.get_dependents(definition)) > 0
  480. def get_dependents(self, definition):
  481. """Gets the set of definitions that depend on the given definition."""
  482. _, incoming_edges = self.dependency_graph[definition]
  483. return incoming_edges
  484. def use(self, definition):
  485. """Creates a tree that produces the given definition's value."""
  486. def_value = cfg_ir.get_def_value(definition)
  487. if isinstance(def_value, LoweringState.inline_value_types):
  488. return self.lowering_state.lower_value(def_value)
  489. elif isinstance(def_value, cfg_ir.AllocateRootNode):
  490. return tree_ir.LoadLocalInstruction(
  491. self.lowering_state.get_root_node_name(def_value))
  492. elif not self.has_scheduled(definition):
  493. return self.schedule(definition)
  494. elif definition.has_value():
  495. return self.lowering_state.create_definition_load(definition)
  496. else:
  497. return tree_ir.EmptyInstruction()
  498. def __schedule_definition(self, definition, store_and_forget):
  499. """Schedules the given definition, excluding definitions that are dependent on it.
  500. A list of trees is returned."""
  501. assert not self.has_scheduled(definition)
  502. statements = []
  503. # print('Scheduling %s; store_and_forget: %s' % (definition, store_and_forget))
  504. self.remove_definition(definition)
  505. if isinstance(definition, cfg_ir.FlowInstruction):
  506. statements.append(self.lowering_state.lower_value(definition))
  507. else:
  508. # We're sure that we're dealing with a bona fide cfg_ir.Definition now.
  509. definition_value = definition.value
  510. if cfg_ir.is_value_def(definition_value, LoweringState.inline_value_types):
  511. pass
  512. elif (not store_and_forget
  513. and definition in self.lowering_state.inlinable_definitions):
  514. statements.append(self.lowering_state.lower_value(definition_value))
  515. else:
  516. lowered_value = self.lowering_state.lower_value(definition_value)
  517. if definition.has_value():
  518. def_load = self.lowering_state.create_definition_load(definition)
  519. statements.append(
  520. tree_ir.IgnoreInstruction(def_load.create_store(lowered_value)))
  521. if not store_and_forget:
  522. statements.append(def_load)
  523. else:
  524. statements.append(lowered_value)
  525. # print('Done scheduling %s; result: %s' % (definition, tree_ir.create_block(*statements)))
  526. return statements
  527. def __schedule_top_of_stack(self, stack, statements, store_and_forget):
  528. """Tries to schedule the given stack's top-of-stack element."""
  529. definition = stack[-1]
  530. if self.has_scheduled(definition):
  531. # Definition has already been scheduled. Load it if it is the only
  532. # element on the stack. Otherwise, do nothing.
  533. stack.pop()
  534. if (not store_and_forget
  535. and definition.has_value()
  536. and not cfg_ir.is_value_def(definition, LoweringState.inline_value_types)):
  537. statements.append(self.lowering_state.create_definition_load(definition))
  538. return statements
  539. dependents = self.get_dependents(definition)
  540. if len(dependents) > 0:
  541. dependent = next(iter(dependents))
  542. stack.append(dependent)
  543. return statements
  544. else:
  545. # Definition has not been scheduled, and all of its dependent definitions
  546. # have been scheduled.
  547. stack.pop()
  548. return self.__schedule_definition(definition, store_and_forget) + statements
  549. def schedule(self, definition, store_and_forget=False):
  550. """Schedules the given definition. The resulting tree is returned."""
  551. assert not self.has_scheduled(definition)
  552. statements = []
  553. schedule_stack = [definition]
  554. while len(schedule_stack) > 0:
  555. statements = self.__schedule_top_of_stack(
  556. schedule_stack, statements, store_and_forget or len(schedule_stack) > 1)
  557. return tree_ir.create_block(*statements)
  558. class LoweringState(object):
  559. """Stores information related to the relooper->tree conversion."""
  560. def __init__(self, jit, inlinable_definitions):
  561. self.jit = jit
  562. self.label_variable = tree_ir.VariableName('__label')
  563. self.definition_loads = {}
  564. self.local_name_map = bytecode_to_tree.LocalNameMap()
  565. self.root_edge_names = {}
  566. self.inlinable_definitions = inlinable_definitions
  567. self.scheduler = None
  568. def __get_root_edge_name(self, root_node):
  569. """Gets the name of the given root edge's variable."""
  570. return self.get_root_node_name(root_node) + '_edge'
  571. def get_root_node_name(self, root_node):
  572. """Gets the name of the given root node's variable."""
  573. if isinstance(root_node, cfg_ir.Definition):
  574. return self.get_root_node_name(root_node.value)
  575. if root_node in self.root_edge_names:
  576. return self.root_edge_names[root_node]
  577. result = 'jit_locals%d' % len(self.root_edge_names)
  578. self.root_edge_names[root_node] = result
  579. return result
  580. def create_definition_load(self, definition):
  581. """Loads the given definition's variable."""
  582. if definition in self.definition_loads:
  583. return self.definition_loads[definition]
  584. result = tree_ir.LoadLocalInstruction(None)
  585. self.definition_loads[definition] = result
  586. return result
  587. def use_definition(self, definition):
  588. """Loads the given definition's value."""
  589. return self.scheduler.use(definition)
  590. def lower_block(self, block):
  591. """Lowers the given (relooped) block to a tree."""
  592. statements = []
  593. self.scheduler = SimpleDefinitionScheduler(self, block.definitions, block.flow)
  594. while 1:
  595. schedulable_def = self.scheduler.get_schedulable_definition()
  596. if schedulable_def is None:
  597. break
  598. statements.append(self.scheduler.schedule(schedulable_def, True))
  599. self.scheduler = None
  600. statements.reverse()
  601. return tree_ir.create_block(*statements)
  602. def lower_value(self, value):
  603. """Lowers the given instruction to a tree."""
  604. value_type = type(value)
  605. if value_type in LoweringState.instruction_lowerings:
  606. return LoweringState.instruction_lowerings[value_type](self, value)
  607. else:
  608. raise jit_runtime.JitCompilationFailedException(
  609. "Unknown CFG instruction: '%s'" % value)
  610. def lower_literal(self, value):
  611. """Lowers the given literal value."""
  612. return tree_ir.LiteralInstruction(value.literal)
  613. def lower_check_local_exists(self, value):
  614. """Lowers a 'check-value-exists' value."""
  615. return tree_ir.LocalExistsInstruction(
  616. self.local_name_map.get_local_name(value.variable.node_id))
  617. def lower_declare_local(self, value):
  618. """Lowers a 'declare-local' value."""
  619. local_name = self.local_name_map.get_local_name(value.variable.node_id)
  620. return tree_ir.create_block(
  621. tree_ir.create_new_local_node(
  622. local_name,
  623. self.use_definition(value.root_node)),
  624. tree_ir.LoadLocalInstruction(local_name))
  625. def lower_resolve_local(self, value):
  626. """Lowers a 'resolve-local' value."""
  627. return tree_ir.LoadLocalInstruction(
  628. self.local_name_map.get_local_name(value.variable.node_id))
  629. def lower_declare_global(self, value):
  630. """Lowers a 'declare-global' value."""
  631. #
  632. # global_var, = yield [("CN", [])]
  633. # _globals, = yield [("RD", [task_root, "globals"])]
  634. # yield [("CD", [_globals, var_name, global_var])]
  635. #
  636. global_var = tree_ir.StoreLocalInstruction(None, tree_ir.CreateNodeInstruction())
  637. return tree_ir.create_block(
  638. global_var.create_store(
  639. tree_ir.CreateNodeInstruction()),
  640. tree_ir.CreateDictionaryEdgeInstruction(
  641. tree_ir.ReadDictionaryValueInstruction(
  642. bytecode_to_tree.load_task_root(),
  643. tree_ir.LiteralInstruction('globals')),
  644. tree_ir.LiteralInstruction(
  645. value.variable.name),
  646. global_var.create_load()),
  647. global_var.create_load())
  648. def lower_resolve_global(self, value):
  649. """Lowers a 'resolve-global' value."""
  650. #
  651. # _globals, = yield [("RD", [task_root, "globals"])]
  652. # global_var, = yield [("RD", [_globals, var_name])]
  653. #
  654. return tree_ir.ReadDictionaryValueInstruction(
  655. tree_ir.ReadDictionaryValueInstruction(
  656. bytecode_to_tree.load_task_root(),
  657. tree_ir.LiteralInstruction('globals')),
  658. tree_ir.LiteralInstruction(value.variable.name))
  659. def lower_function_parameter(self, value):
  660. """Lowers a 'function-parameter' value."""
  661. return tree_ir.LoadLocalInstruction(value.name)
  662. def lower_alloc_root_node(self, value):
  663. """Lowers an 'alloc-root-node' value."""
  664. local_name = tree_ir.VariableName(self.get_root_node_name(value))
  665. return tree_ir.create_block(
  666. tree_ir.create_new_local_node(
  667. local_name,
  668. tree_ir.LoadIndexInstruction(
  669. tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME),
  670. tree_ir.LiteralInstruction('task_root')),
  671. self.__get_root_edge_name(value)),
  672. tree_ir.LoadLocalInstruction(local_name))
  673. def lower_free_root_node(self, value):
  674. """Lowers a 'free-root-node' value."""
  675. return tree_ir.DeleteEdgeInstruction(
  676. tree_ir.LoadLocalInstruction(self.__get_root_edge_name(value.root_node)))
  677. def lower_load_pointer(self, value):
  678. """Lowers a 'load' value."""
  679. return bytecode_to_tree.create_access(self.use_definition(value.pointer))
  680. def lower_store_pointer(self, value):
  681. """Lowers a 'store' value."""
  682. ptr, val = self.use_definition(value.pointer), self.use_definition(value.value)
  683. return bytecode_to_tree.create_assign(ptr, val)
  684. def lower_read(self, value):
  685. """Lowers a 'read' value."""
  686. return tree_ir.ReadValueInstruction(
  687. self.use_definition(value.node))
  688. def lower_create_node(self, value):
  689. """Lowers a 'create-node' value."""
  690. if value.value.has_value():
  691. return tree_ir.CreateNodeWithValueInstruction(
  692. self.use_definition(value.value))
  693. else:
  694. return tree_ir.CreateNodeInstruction()
  695. def lower_create_edge(self, value):
  696. """Lowers a 'create-edge' value."""
  697. source, target = self.use_definition(value.source), self.use_definition(value.target)
  698. return tree_ir.CreateEdgeInstruction(source, target)
  699. def lower_binary(self, value):
  700. """Lowers a 'binary' value."""
  701. lhs, rhs = self.use_definition(value.lhs), self.use_definition(value.rhs)
  702. return tree_ir.BinaryInstruction(lhs, value.operator, rhs)
  703. def lower_unary(self, value):
  704. """Lowers a 'unary' value."""
  705. operand = self.use_definition(value.operand)
  706. return tree_ir.UnaryInstruction(value.operator, operand)
  707. def lower_direct_call(self, value):
  708. """Lowers a direct function call."""
  709. calling_convention = value.calling_convention
  710. if calling_convention in LoweringState.call_lowerings:
  711. return LoweringState.call_lowerings[calling_convention](self, value)
  712. else:
  713. raise jit_runtime.JitCompilationFailedException(
  714. "Unknown calling convention: '%s' in instruction '%s'" %
  715. (calling_convention, value))
  716. def lower_indirect_call(self, value):
  717. """Lowers an indirect function call."""
  718. target = self.use_definition(value.target)
  719. args = [(name, self.use_definition(arg))
  720. for name, arg in value.argument_list]
  721. return bytecode_to_tree.create_indirect_call(target, args)
  722. def lower_simple_positional_call(self, value):
  723. """Lowers a direct call that uses the 'simple-positional' calling convention."""
  724. return tree_ir.CallInstruction(
  725. tree_ir.LoadGlobalInstruction(value.target_name),
  726. [self.use_definition(arg) for _, arg in value.argument_list])
  727. def lower_self_positional_call(self, value):
  728. """Lowers a direct call that uses the 'self-positional' calling convention."""
  729. all_args = [self.use_definition(arg) for _, arg in value.argument_list]
  730. target = all_args[0]
  731. arg_list = all_args[1:]
  732. return tree_ir.CallInstruction(
  733. tree_ir.LoadMemberInstruction(target, value.target_name), arg_list)
  734. def lower_jit_call(self, value):
  735. """Lowers a direct call that uses the 'jit' calling convention."""
  736. arg_list = [(name, self.use_definition(arg))
  737. for name, arg in value.argument_list]
  738. intrinsic = self.jit.get_intrinsic(value.target_name)
  739. if intrinsic is not None:
  740. return bytecode_to_tree.apply_intrinsic(intrinsic, arg_list)
  741. else:
  742. return tree_ir.create_jit_call(
  743. tree_ir.LoadGlobalInstruction(value.target_name),
  744. arg_list,
  745. tree_ir.LoadLocalInstruction(jit_runtime.KWARGS_PARAMETER_NAME))
  746. def lower_macro_call(self, value):
  747. """Expands a macro call."""
  748. arg_list = [self.use_definition(arg) for _, arg in value.argument_list]
  749. if value.target_name in LoweringState.macro_lowerings:
  750. return LoweringState.macro_lowerings[value.target_name](*arg_list)
  751. else:
  752. raise jit_runtime.JitCompilationFailedException(
  753. "Unknown macro: '%s' in instruction '%s'" %
  754. (value.target_name, value))
  755. def lower_io_call(self, value):
  756. """Expands an IO call."""
  757. arg_list = [self.use_definition(arg) for _, arg in value.argument_list]
  758. if value.target_name == cfg_ir.NOP_MACRO_NAME:
  759. return tree_ir.NopInstruction()
  760. elif value.target_name == cfg_ir.INPUT_MACRO_NAME:
  761. return bytecode_to_tree.create_input(self.jit.use_input_function)
  762. elif value.target_name == cfg_ir.OUTPUT_MACRO_NAME:
  763. return bytecode_to_tree.create_output(*arg_list)
  764. else:
  765. raise jit_runtime.JitCompilationFailedException(
  766. "Unknown IO macro: '%s' in instruction '%s'" %
  767. (value.target_name, value))
  768. def lower_jump(self, flow):
  769. """Lowers the given 'jump' flow instruction to a tree."""
  770. return self.lower_branch(flow.branch)
  771. def lower_select(self, flow):
  772. """Lowers the given 'select' flow instruction to a tree."""
  773. # Schedule all branch arguments, so their definitions don't end up in the
  774. # 'if' statement.
  775. statements = []
  776. for branch in (flow.if_branch, flow.else_branch):
  777. for arg in branch.arguments:
  778. if not self.scheduler.has_scheduled(arg):
  779. statements.append(self.scheduler.schedule(arg, True))
  780. statements.append(
  781. tree_ir.SelectInstruction(
  782. self.use_definition(flow.condition),
  783. self.lower_branch(flow.if_branch),
  784. self.lower_branch(flow.else_branch)))
  785. return tree_ir.create_block(*statements)
  786. def lower_return(self, flow):
  787. """Lowers the given 'return' flow instruction to a tree."""
  788. return tree_ir.ReturnInstruction(self.use_definition(flow.value))
  789. def lower_throw(self, flow):
  790. """Lowers the given 'throw' flow instruction to a tree."""
  791. return tree_ir.RaiseInstruction(self.use_definition(flow.exception))
  792. def lower_unreachable(self, _):
  793. """Lowers the given 'unreachable' flow instruction to a tree."""
  794. return tree_ir.IgnoreInstruction(
  795. tree_ir.CallInstruction(
  796. tree_ir.LoadGlobalInstruction(jit_runtime.UNREACHABLE_FUNCTION_NAME), []))
  797. def lower_break(self, _):
  798. """Lowers the given 'break' flow instruction to a tree."""
  799. return tree_ir.BreakInstruction()
  800. def lower_continue(self, _):
  801. """Lowers the given 'continue' flow instruction to a tree."""
  802. return tree_ir.ContinueInstruction()
  803. def lower_branch(self, branch):
  804. """Lowers the given (relooped) branch to a tree."""
  805. instructions = []
  806. if len(branch.arguments) > 0:
  807. instructions.append(
  808. tree_ir.TupleStoreLocalInstruction(
  809. [(self.create_definition_load(param).name, self.use_definition(arg))
  810. for param, arg in zip(branch.block.parameters, branch.arguments)]))
  811. instructions.append(
  812. tree_ir.IgnoreInstruction(
  813. tree_ir.StoreLocalInstruction(
  814. self.label_variable,
  815. tree_ir.LiteralInstruction(branch.block.index))))
  816. return tree_ir.create_block(*instructions)
  817. inline_value_types = (cfg_ir.Literal, cfg_ir.ResolveLocal, cfg_ir.FunctionParameter)
  818. instruction_lowerings = {
  819. cfg_ir.Literal : lower_literal,
  820. cfg_ir.CheckLocalExists : lower_check_local_exists,
  821. cfg_ir.DeclareLocal : lower_declare_local,
  822. cfg_ir.ResolveLocal : lower_resolve_local,
  823. cfg_ir.DeclareGlobal : lower_declare_global,
  824. cfg_ir.ResolveGlobal : lower_resolve_global,
  825. cfg_ir.FunctionParameter : lower_function_parameter,
  826. cfg_ir.AllocateRootNode : lower_alloc_root_node,
  827. cfg_ir.DeallocateRootNode : lower_free_root_node,
  828. cfg_ir.LoadPointer : lower_load_pointer,
  829. cfg_ir.StoreAtPointer : lower_store_pointer,
  830. cfg_ir.Read : lower_read,
  831. cfg_ir.CreateNode : lower_create_node,
  832. cfg_ir.CreateEdge : lower_create_edge,
  833. cfg_ir.Binary : lower_binary,
  834. cfg_ir.Unary : lower_unary,
  835. cfg_ir.DirectFunctionCall : lower_direct_call,
  836. cfg_ir.IndirectFunctionCall : lower_indirect_call,
  837. cfg_ir.JumpFlow : lower_jump,
  838. cfg_ir.SelectFlow : lower_select,
  839. cfg_ir.ReturnFlow : lower_return,
  840. cfg_ir.ThrowFlow : lower_throw,
  841. cfg_ir.UnreachableFlow : lower_unreachable,
  842. BreakFlow : lower_break,
  843. ContinueFlow : lower_continue
  844. }
  845. call_lowerings = {
  846. cfg_ir.SIMPLE_POSITIONAL_CALLING_CONVENTION : lower_simple_positional_call,
  847. cfg_ir.SELF_POSITIONAL_CALLING_CONVENTION : lower_self_positional_call,
  848. cfg_ir.JIT_CALLING_CONVENTION : lower_jit_call,
  849. cfg_ir.JIT_NO_GC_CALLING_CONVENTION : lower_jit_call,
  850. cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION : lower_macro_call,
  851. cfg_ir.MACRO_IO_CALLING_CONVENTION : lower_io_call
  852. }
  853. macro_lowerings = {
  854. cfg_ir.PRINT_MACRO_NAME: tree_ir.PrintInstruction,
  855. cfg_ir.READ_OUTGOING_EDGES_MACRO_NAME: tree_ir.ReadOutgoingEdgesInstruction,
  856. cfg_ir.READ_DICT_KEYS_MACRO_NAME: tree_ir.ReadDictionaryKeysInstruction,
  857. cfg_ir.INDEX_MACRO_NAME: tree_ir.LoadIndexInstruction,
  858. cfg_ir.REVERSE_LIST_MACRO_NAME:
  859. lambda seq:
  860. tree_ir.ListSliceInstruction(seq, None, None, tree_ir.LiteralInstruction(-1)),
  861. cfg_ir.GC_PROTECT_MACRO_NAME: lower_gc_protect,
  862. cfg_ir.MAYBE_GC_PROTECT_MACRO_NAME: lower_maybe_gc_protect,
  863. cfg_ir.REGISTER_DEBUG_INFO_MACRO_NAME: lower_register_debug_info
  864. }
  865. def lower_flow_graph(entry_point, jit):
  866. """Lowers the control-flow graph defined by the given entry point to tree IR."""
  867. cfg_lowerer = LoweringState(jit, find_inlinable_definitions(entry_point))
  868. ep_branches = entry_point.flow.branches()
  869. if len(ep_branches) == 0:
  870. lowered_body = cfg_lowerer.lower_block(entry_point)
  871. else:
  872. lowered_body = reloop_function_body(entry_point).lower(cfg_lowerer)
  873. lowered_body = tree_ir.CompoundInstruction(
  874. cfg_lowerer.lower_jump(cfg_ir.create_jump(entry_point)), lowered_body)
  875. return lowered_body