seafoam 0.15 → 0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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