cfg_to_tree.py 43 KB

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