seafoam 0.15 → 0.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/seafoam/bgv/bgv_parser.rb +2 -2
- data/lib/seafoam/commands.rb +5 -4
- data/lib/seafoam/graal/pi.rb +2 -0
- data/lib/seafoam/graph.rb +5 -1
- data/lib/seafoam/json_writer.rb +1 -1
- data/lib/seafoam/mermaid_writer.rb +1 -1
- data/lib/seafoam/passes/graal.rb +76 -21
- data/lib/seafoam/passes/truffle.rb +8 -8
- data/lib/seafoam/passes/truffle_translators/translators.rb +10 -0
- data/lib/seafoam/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1fc3a85c02d7d1af7d3835e43359cff6032b617cf3dee449f1519549aef1262
|
4
|
+
data.tar.gz: d5a947fe97c401206f51b56fcdbc1fd97afda7b6c8924c6b842cbb67043dcc4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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(
|
164
|
+
name = graph_header[:format].sub("%s") do
|
165
165
|
arg = graph_header[:args][count]
|
166
166
|
count += 1
|
167
167
|
arg
|
data/lib/seafoam/commands.rb
CHANGED
@@ -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
|
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)
|
data/lib/seafoam/graal/pi.rb
CHANGED
data/lib/seafoam/graph.rb
CHANGED
data/lib/seafoam/json_writer.rb
CHANGED
@@ -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
|
|
data/lib/seafoam/passes/graal.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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(
|
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(
|
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(
|
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
|
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 = [
|
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 = [
|
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
|
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
|
35
|
-
next unless array_node.node_class
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
data/lib/seafoam/version.rb
CHANGED
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.
|
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:
|
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.
|
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
|