cfg_to_tree.py 44 KB

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