seafoam 0.15 → 0.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30a5f036d6ae3873ad70b1f113fe00015678efe83e5e2dfdbac8e7a2556e76ec
4
- data.tar.gz: f8ee8f98c42e1d3a82a47fa58f50867553b5ffe1d532ed4ccebba9224314a4c0
3
+ metadata.gz: f1fc3a85c02d7d1af7d3835e43359cff6032b617cf3dee449f1519549aef1262
4
+ data.tar.gz: d5a947fe97c401206f51b56fcdbc1fd97afda7b6c8924c6b842cbb67043dcc4c
5
5
  SHA512:
6
- metadata.gz: 0bd1f351f0e43737ba6182d156afd405b2d3d69b4b2804c8ef7d556062d857787b7d278a8edfb26462ed65add584a08741968244e9ab47b082a28783a16064a0
7
- data.tar.gz: 4da1e9d9c3a5d27c94efbd37fb698820d3d44344b27f2eb030223f045b0f4ba358df2df9e0a0d4becb0b50e862700f493f8c6e587a3cf6b38b58df703468bd99
6
+ metadata.gz: 7c98e42cf49caa080482e60bd92560b8e3c4eb3a9f2f9f57f50c2c3ff75132d10a6e96a08310865a6d6647dd2d2a1a6be81f86c432404129a37969e1b5866966
7
+ data.tar.gz: 64213bcdb28b5951839d0d4a2e3eef51cac840880f6707849d9bd7830ad517192dd4b0542530d1d15c439d5d14c4f19fddc758e4f0096d5940d7207f82a4c674
@@ -59,7 +59,7 @@ module Seafoam
59
59
  # Move to the next graph in the file, and return its index and ID, or nil if
60
60
  # there are no more graphs.
61
61
  def read_graph_preheader
62
- return nil unless read_groups
62
+ return unless read_groups
63
63
 
64
64
  # Already read BEGIN_GRAPH
65
65
  index = @index
@@ -161,7 +161,7 @@ module Seafoam
161
161
  def graph_name(graph_header)
162
162
  groups_names = graph_header[:group].map { |g| g[:short_name] }
163
163
  count = 0
164
- name = graph_header[:format].sub(/%s/) do
164
+ name = graph_header[:format].sub("%s") do
165
165
  arg = graph_header[:args][count]
166
166
  count += 1
167
167
  arg
@@ -394,11 +394,12 @@ module Seafoam
394
394
  description.node_counts[simple_node_class] += 1
395
395
 
396
396
  case node_class
397
- when "org.graalvm.compiler.nodes.IfNode"
397
+ when "org.graalvm.compiler.nodes.IfNode", "jdk.graal.compiler.nodes.IfNode"
398
398
  description.branches = true
399
- when "org.graalvm.compiler.nodes.LoopBeginNode"
399
+ when "org.graalvm.compiler.nodes.LoopBeginNode", "jdk.graal.compiler.nodes.LoopBeginNode"
400
400
  description.loops = true
401
- when "org.graalvm.compiler.nodes.InvokeNode", "org.graalvm.compiler.nodes.InvokeWithExceptionNode"
401
+ when "org.graalvm.compiler.nodes.InvokeNode", "org.graalvm.compiler.nodes.InvokeWithExceptionNode",
402
+ "jdk.graal.compiler.nodes.InvokeNode", "jdk.graal.compiler.nodes.InvokeWithExceptionNode"
402
403
  description.calls = true
403
404
  end
404
405
  elsif node.props[:synthetic_class]
@@ -406,7 +407,7 @@ module Seafoam
406
407
  end
407
408
 
408
409
  description.deopts = graph.nodes[0].outputs.map(&:to)
409
- .all? { |t| t.node_class == "org.graalvm.compiler.nodes.DeoptimizeNode" }
410
+ .all? { |t| t.node_class.end_with?(".compiler.nodes.DeoptimizeNode") }
410
411
  end
411
412
 
412
413
  formatter = formatter_module::DescribeFormatter.new(graph, description)
@@ -16,6 +16,8 @@ module Seafoam
16
16
  PI_NODES = [
17
17
  "org.graalvm.compiler.nodes.PiNode",
18
18
  "org.graalvm.compiler.nodes.PiArrayNode",
19
+ "jdk.graal.compiler.nodes.PiNode",
20
+ "jdk.graal.compiler.nodes.PiArrayNode",
19
21
  ]
20
22
  end
21
23
  end
data/lib/seafoam/graph.rb CHANGED
@@ -63,7 +63,11 @@ module Seafoam
63
63
  end
64
64
 
65
65
  def node_class
66
- @props.dig(:node_class, :node_class)
66
+ if @props[:synthetic] == true
67
+ @props[:synthetic_class]
68
+ else
69
+ @props.dig(:node_class, :node_class)
70
+ end
67
71
  end
68
72
 
69
73
  # All edges - input and output.
@@ -35,7 +35,7 @@ module Seafoam
35
35
  edges: edges,
36
36
  }
37
37
 
38
- @out.puts JSON.pretty_generate(prepare_json(object))
38
+ @out.puts JSON.pretty_generate(self.class.prepare_json(object))
39
39
  end
40
40
 
41
41
  class << self
@@ -38,7 +38,7 @@ module Seafoam
38
38
  when "diamond"
39
39
  shape = ["{{", "}}"]
40
40
  end
41
- @stream.puts "#{indent}#{id}#{shape[0]}#{attrs[:label].inspect}#{shape[1]}"
41
+ @stream.puts "#{indent}#{id}#{shape[0]}#{attrs[:label].gsub('"', "#quot;").inspect}#{shape[1]}"
42
42
  @stream.puts "#{indent}style #{id} fill:#{attrs[:fillcolor]},stroke:#{attrs[:color]},color:#{attrs[:fontcolor]};"
43
43
  end
44
44
 
@@ -42,7 +42,7 @@ module Seafoam
42
42
  # For constant nodes, the rawvalue is a truncated version of the
43
43
  # actual value, which is fully qualified. Instead, produce a simple
44
44
  # version of the value and don't truncate it.
45
- if node_class == "org.graalvm.compiler.nodes.ConstantNode"
45
+ if node_class.end_with?(".compiler.nodes.ConstantNode")
46
46
  if node.props["value"] =~ /Object\[Instance<(\w+\.)+(\w*)>\]/
47
47
  node.props["rawvalue"] = "instance:#{Regexp.last_match(2)}"
48
48
  end
@@ -59,63 +59,63 @@ module Seafoam
59
59
  end
60
60
 
61
61
  # The template for InvokeNode could be simpler.
62
- if node_class == "org.graalvm.compiler.nodes.InvokeNode"
62
+ if node_class.end_with?(".compiler.nodes.InvokeNode")
63
63
  name_template = "Call {p#targetMethod/s}"
64
64
  end
65
65
 
66
66
  # The template for InvokeWithExceptionNode could be simpler.
67
- if node_class == "org.graalvm.compiler.nodes.InvokeWithExceptionNode"
67
+ if node_class.end_with?(".compiler.nodes.InvokeWithExceptionNode")
68
68
  name_template = "Call {p#targetMethod/s} !"
69
69
  end
70
70
 
71
71
  # The template for CommitAllocationNode could be simpler.
72
- if node_class == "org.graalvm.compiler.nodes.virtual.CommitAllocationNode"
72
+ if node_class.end_with?(".compiler.nodes.virtual.CommitAllocationNode")
73
73
  name_template = "Alloc"
74
74
  end
75
75
 
76
76
  # The template for org.graalvm.compiler.nodes.virtual.VirtualArrayNode
77
77
  # includes an ID that we don't normally need.
78
- if node_class == "org.graalvm.compiler.nodes.virtual.VirtualArrayNode"
78
+ if node_class.end_with?(".compiler.nodes.virtual.VirtualArrayNode")
79
79
  name_template = "VirtualArray {p#componentType/s}[{p#length}]"
80
80
  end
81
81
 
82
82
  # The template for LoadField could be simpler.
83
- if node_class == "org.graalvm.compiler.nodes.java.LoadFieldNode"
83
+ if node_class.end_with?(".compiler.nodes.java.LoadFieldNode")
84
84
  name_template = "LoadField {x#field}"
85
85
  end
86
86
 
87
87
  # The template for StoreField could be simpler.
88
- if node_class == "org.graalvm.compiler.nodes.java.StoreFieldNode"
88
+ if node_class.end_with?(".compiler.nodes.java.StoreFieldNode")
89
89
  name_template = "StoreField {x#field}"
90
90
  end
91
91
 
92
92
  # We want to see keys for IntegerSwitchNode.
93
- if node_class == "org.graalvm.compiler.nodes.extended.IntegerSwitchNode"
93
+ if node_class.end_with?(".compiler.nodes.extended.IntegerSwitchNode")
94
94
  name_template = "IntegerSwitch {p#keys}"
95
95
  end
96
96
 
97
97
  # Use a symbol for PiNode.
98
- if node_class == "org.graalvm.compiler.nodes.PiNode"
98
+ if node_class.end_with?(".compiler.nodes.PiNode")
99
99
  name_template = "π"
100
100
  end
101
101
 
102
102
  # Use a symbol for PiArrayNode.
103
- if node_class == "org.graalvm.compiler.nodes.PiArrayNode"
103
+ if node_class.end_with?(".compiler.nodes.PiArrayNode")
104
104
  name_template = "[π]"
105
105
  end
106
106
 
107
107
  # Use a symbol for PhiNode.
108
- if node_class == "org.graalvm.compiler.nodes.ValuePhiNode"
108
+ if node_class.end_with?(".compiler.nodes.ValuePhiNode")
109
109
  name_template = "ϕ"
110
110
  end
111
111
 
112
112
  # Better template for frame states.
113
- if node_class == "org.graalvm.compiler.nodes.FrameState"
113
+ if node_class.end_with?(".compiler.nodes.FrameState")
114
114
  name_template = "FrameState {x#state}"
115
115
  end
116
116
 
117
117
  # Show the stamp in an InstanceOfNode.
118
- if node_class == "org.graalvm.compiler.nodes.java.InstanceOfNode"
118
+ if node_class.end_with?(".compiler.nodes.java.InstanceOfNode")
119
119
  name_template = "InstanceOf {x#simpleStamp}"
120
120
  end
121
121
 
@@ -194,6 +194,49 @@ module Seafoam
194
194
  "org.graalvm.compiler.replacements.nodes.ReadRegisterNode" => "memory",
195
195
  "org.graalvm.compiler.replacements.nodes.WriteRegisterNode" => "memory",
196
196
  "org.graalvm.compiler.word.WordCastNode" => "memory",
197
+ "jdk.graal.compiler.nodes.BeginNode" => "control",
198
+ "jdk.graal.compiler.nodes.ConstantNode" => "input",
199
+ "jdk.graal.compiler.nodes.DeoptimizeNode" => "control",
200
+ "jdk.graal.compiler.nodes.EndNode" => "control",
201
+ "jdk.graal.compiler.nodes.extended.IntegerSwitchNode" => "control",
202
+ "jdk.graal.compiler.nodes.extended.UnsafeMemoryLoadNode" => "memory",
203
+ "jdk.graal.compiler.nodes.extended.UnsafeMemoryStoreNode" => "memory",
204
+ "jdk.graal.compiler.nodes.FixedGuardNode" => "guard",
205
+ "jdk.graal.compiler.nodes.FrameState" => "info",
206
+ "jdk.graal.compiler.nodes.GuardNode" => "guard",
207
+ "jdk.graal.compiler.nodes.IfNode" => "control",
208
+ "jdk.graal.compiler.nodes.InvokeNode" => "call",
209
+ "jdk.graal.compiler.nodes.InvokeWithExceptionNode" => "call",
210
+ "jdk.graal.compiler.nodes.java.ArrayLengthNode" => "memory",
211
+ "jdk.graal.compiler.nodes.java.LoadFieldNode" => "memory",
212
+ "jdk.graal.compiler.nodes.java.LoadIndexedNode" => "memory",
213
+ "jdk.graal.compiler.nodes.java.MonitorEnterNode" => "sync",
214
+ "jdk.graal.compiler.nodes.java.MonitorExitNode" => "sync",
215
+ "jdk.graal.compiler.nodes.java.NewArrayNode" => "alloc",
216
+ "jdk.graal.compiler.nodes.java.NewInstanceNode" => "alloc",
217
+ "jdk.graal.compiler.nodes.java.RawMonitorEnterNode" => "sync",
218
+ "jdk.graal.compiler.nodes.java.StoreFieldNode" => "memory",
219
+ "jdk.graal.compiler.nodes.java.StoreIndexedNode" => "memory",
220
+ "jdk.graal.compiler.nodes.KillingBeginNode" => "control",
221
+ "jdk.graal.compiler.nodes.LoopBeginNode" => "control",
222
+ "jdk.graal.compiler.nodes.LoopEndNode" => "control",
223
+ "jdk.graal.compiler.nodes.LoopExitNode" => "control",
224
+ "jdk.graal.compiler.nodes.memory.ReadNode" => "memory",
225
+ "jdk.graal.compiler.nodes.memory.WriteNode" => "memory",
226
+ "jdk.graal.compiler.nodes.MergeNode" => "control",
227
+ "jdk.graal.compiler.nodes.ParameterNode" => "input",
228
+ "jdk.graal.compiler.nodes.PrefetchAllocateNode" => "alloc",
229
+ "jdk.graal.compiler.nodes.ReturnNode" => "control",
230
+ "jdk.graal.compiler.nodes.StartNode" => "control",
231
+ "jdk.graal.compiler.nodes.UnwindNode" => "control",
232
+ "jdk.graal.compiler.nodes.virtual.AllocatedObjectNode" => "virtual",
233
+ "jdk.graal.compiler.nodes.virtual.CommitAllocationNode" => "alloc",
234
+ "jdk.graal.compiler.nodes.virtual.VirtualArrayNode" => "virtual",
235
+ "jdk.graal.compiler.nodes.VirtualObjectState" => "info",
236
+ "jdk.graal.compiler.replacements.nodes.ArrayEqualsNode" => "memory",
237
+ "jdk.graal.compiler.replacements.nodes.ReadRegisterNode" => "memory",
238
+ "jdk.graal.compiler.replacements.nodes.WriteRegisterNode" => "memory",
239
+ "jdk.graal.compiler.word.WordCastNode" => "memory",
197
240
  }
198
241
 
199
242
  # Render a Graal 'name template'.
@@ -208,10 +251,10 @@ module Seafoam
208
251
  e.props[:name] == Regexp.last_match(1)
209
252
  end.map { |e| e.from.id }.join(", ")
210
253
  end
211
- string = string.gsub(/\{x#field\}/) do |_|
254
+ string = string.gsub("{x#field}") do |_|
212
255
  "#{node.props.dig("field", :field_class).split(".").last}.#{node.props.dig("field", :name)}"
213
256
  end
214
- string = string.gsub(/\{x#state\}/) do |_|
257
+ string = string.gsub("{x#state}") do |_|
215
258
  "#{node.props.dig(
216
259
  "code",
217
260
  :declaring_class,
@@ -220,7 +263,7 @@ module Seafoam
220
263
  :method_name,
221
264
  )} #{node.props["sourceFile"]}:#{node.props["sourceLine"]}"
222
265
  end
223
- string = string.gsub(/\{x#simpleStamp\}/) do |_|
266
+ string = string.gsub("{x#simpleStamp}") do |_|
224
267
  stamp = node.props.dig("checkedStamp")
225
268
  if stamp =~ /a!?# L(.*);/
226
269
  Regexp.last_match(1)
@@ -234,7 +277,7 @@ module Seafoam
234
277
  # Annotate edges with their label and kind.
235
278
  def apply_edges(graph)
236
279
  graph.edges.each do |edge|
237
- if edge.to.node_class == "org.graalvm.compiler.nodes.ValuePhiNode" && edge.props[:name] == "values"
280
+ if edge.to.node_class.end_with?(".compiler.nodes.ValuePhiNode") && edge.props[:name] == "values"
238
281
  merge_node = edge.to.edges.find { |e| e.props[:name] == "merge" }.from
239
282
  control_into_merge = ["ends", "loopBegin"]
240
283
  merge_node_control_edges_in = merge_node.edges.select do |e|
@@ -282,11 +325,11 @@ module Seafoam
282
325
  # (break) to the LoopBeginNode. Both are drawn reversed.
283
326
  when "loopBegin"
284
327
  case edge.to.node_class
285
- when "org.graalvm.compiler.nodes.LoopEndNode"
328
+ when "org.graalvm.compiler.nodes.LoopEndNode", "jdk.graal.compiler.nodes.LoopEndNode"
286
329
  # If it's from the LoopEnd then it's the control edge to follow.
287
330
  edge.props[:kind] = "loop"
288
331
  edge.props[:reverse] = true
289
- when "org.graalvm.compiler.nodes.LoopExitNode"
332
+ when "org.graalvm.compiler.nodes.LoopExitNode", "jdk.graal.compiler.nodes.LoopExitNode"
290
333
  # If it's from the LoopExit then it's just for information - it's
291
334
  # not control flow to follow.
292
335
  edge.props[:kind] = "info"
@@ -424,15 +467,27 @@ module Seafoam
424
467
  TRIGGERS = ["HostedGraphBuilderPhase", "GraalCompiler", "TruffleCompiler", "SubstrateCompilation"]
425
468
 
426
469
  # Simple input node classes that may be inlined.
427
- SIMPLE_INPUTS = ["org.graalvm.compiler.nodes.ConstantNode", "org.graalvm.compiler.nodes.ParameterNode"]
470
+ SIMPLE_INPUTS = [
471
+ "org.graalvm.compiler.nodes.ConstantNode",
472
+ "org.graalvm.compiler.nodes.ParameterNode",
473
+ "jdk.graal.compiler.nodes.ConstantNode",
474
+ "jdk.graal.compiler.nodes.ParameterNode",
475
+ ]
428
476
 
429
477
  # Nodes just to maintain frame state.
430
478
  FRAME_STATE_NODES = [
431
479
  "org.graalvm.compiler.nodes.FrameState",
432
480
  "org.graalvm.compiler.virtual.nodes.MaterializedObjectState",
481
+ "jdk.graal.compiler.nodes.FrameState",
482
+ "jdk.graal.compiler.virtual.nodes.MaterializedObjectState",
433
483
  ]
434
484
 
435
- BEGIN_END_NODES = ["org.graalvm.compiler.nodes.BeginNode", "org.graalvm.compiler.nodes.EndNode"]
485
+ BEGIN_END_NODES = [
486
+ "org.graalvm.compiler.nodes.BeginNode",
487
+ "org.graalvm.compiler.nodes.EndNode",
488
+ "jdk.graal.compiler.nodes.BeginNode",
489
+ "jdk.graal.compiler.nodes.EndNode",
490
+ ]
436
491
  end
437
492
  end
438
493
  end
@@ -26,13 +26,13 @@ module Seafoam
26
26
  # like a Graal parameter node.
27
27
  def simplify_truffle_args(graph)
28
28
  graph.nodes.dup.each_value do |node|
29
- next unless node.node_class == "org.graalvm.compiler.nodes.java.LoadIndexedNode"
29
+ next unless node.node_class.end_with?(".compiler.nodes.java.LoadIndexedNode")
30
30
 
31
31
  index_node = node.inputs.find { |edge| edge.props[:name] == "index" }.from
32
32
  array_node = Graal::Pi.follow_pi_object(node.inputs.find { |edge| edge.props[:name] == "array" }.from)
33
33
 
34
- next unless index_node.node_class == "org.graalvm.compiler.nodes.ConstantNode"
35
- next unless array_node.node_class == "org.graalvm.compiler.nodes.ParameterNode"
34
+ next unless index_node.node_class.end_with?(".compiler.nodes.ConstantNode")
35
+ next unless array_node.node_class.end_with?(".compiler.nodes.ParameterNode")
36
36
 
37
37
  node.props[:truffle_arg_load] = true
38
38
 
@@ -69,7 +69,7 @@ module Seafoam
69
69
  # are constants that are null or 0.
70
70
  def simplify_alloc(graph)
71
71
  commit_allocation_nodes = graph.nodes.each_value.select do |node|
72
- node.node_class == "org.graalvm.compiler.nodes.virtual.CommitAllocationNode"
72
+ node.node_class.end_with?(".compiler.nodes.virtual.CommitAllocationNode")
73
73
  end
74
74
 
75
75
  commit_allocation_nodes.each do |commit_allocation_node|
@@ -95,7 +95,7 @@ module Seafoam
95
95
  class_name, values = m.captures
96
96
  values = values.split(",").map(&:to_i)
97
97
  virtual_node = graph.nodes[virtual_id]
98
- if virtual_node.node_class == "org.graalvm.compiler.nodes.virtual.VirtualArrayNode"
98
+ if virtual_node.node_class.end_with?(".compiler.nodes.virtual.VirtualArrayNode")
99
99
  label = "New #{class_name[0...-1]}#{virtual_node.props["length"]}]"
100
100
  fields = values.size.times.to_a
101
101
  else
@@ -132,7 +132,7 @@ module Seafoam
132
132
  graph.create_edge(prev, new_node, control_flow_pred.props)
133
133
 
134
134
  allocated_object_node = virtual_node.outputs.find do |output|
135
- output.to.node_class == "org.graalvm.compiler.nodes.virtual.AllocatedObjectNode"
135
+ output.to.node_class.end_with?(".compiler.nodes.virtual.AllocatedObjectNode")
136
136
  end
137
137
  if allocated_object_node
138
138
  allocated_object_node = allocated_object_node.to
@@ -147,7 +147,7 @@ module Seafoam
147
147
  fields.zip(values) do |field, value_id|
148
148
  value_node = virtual_to_object[value_id]&.first || graph.nodes[value_id]
149
149
  if @options[:hide_null_fields] &&
150
- (value_node.node_class == "org.graalvm.compiler.nodes.ConstantNode") &&
150
+ value_node.node_class.end_with?(".compiler.nodes.ConstantNode") &&
151
151
  ["Object[null]", "0"].include?(value_node.props["rawvalue"])
152
152
  value_node.props[:hidden] = true
153
153
  else
@@ -168,7 +168,7 @@ module Seafoam
168
168
  # Hide reachability fences - they're just really boring.
169
169
  def hide_reachability_fences(graph)
170
170
  graph.nodes.each_value do |node|
171
- next unless node.node_class == "org.graalvm.compiler.nodes.java.ReachabilityFenceNode"
171
+ next unless node.node_class.end_with?(".compiler.nodes.java.ReachabilityFenceNode")
172
172
 
173
173
  pred = node.inputs.find { |edge| edge.props[:name] == "next" }
174
174
  succ = node.outputs.find { |edge| edge.props[:name] == "next" }
@@ -21,6 +21,16 @@ module Seafoam
21
21
  subpackage = declaring_class[:declaring_class].match(/^(\w+\.\w+)\./)[1]
22
22
  translator = TRUFFLE_LANGUAGES[subpackage]
23
23
 
24
+ unless translator
25
+ Seafoam::Graal::Source.walk(entry.props.dig("nodeSourcePosition")) do |method|
26
+ declaring_class = method[:declaring_class]
27
+ subpackage = declaring_class.match(/^(\w+\.\w+)\./)[1]
28
+ translator = TRUFFLE_LANGUAGES[subpackage]
29
+
30
+ break if translator
31
+ end
32
+ end
33
+
24
34
  break const_get(translator).new if translator
25
35
  end
26
36
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Seafoam
4
4
  MAJOR_VERSION = 0
5
- MINOR_VERSION = 15
5
+ MINOR_VERSION = 17
6
6
  VERSION = "#{MAJOR_VERSION}.#{MINOR_VERSION}"
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seafoam
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.15'
4
+ version: '0.17'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Seaton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-04 00:00:00.000000000 Z
11
+ date: 2024-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -114,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  - !ruby/object:Gem::Version
115
115
  version: '0'
116
116
  requirements: []
117
- rubygems_version: 3.3.3
117
+ rubygems_version: 3.5.6
118
118
  signing_key:
119
119
  specification_version: 4
120
120
  summary: A tool for working with compiler graphs