seafoam 0.9 → 0.13
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 +4 -4
- data/lib/seafoam/bgv/bgv_parser.rb +1 -2
- data/lib/seafoam/commands.rb +133 -88
- data/lib/seafoam/formatters/base.rb +88 -0
- data/lib/seafoam/formatters/formatters.rb +8 -0
- data/lib/seafoam/formatters/json.rb +82 -0
- data/lib/seafoam/formatters/text.rb +70 -0
- data/lib/seafoam/graal/graph_description.rb +21 -0
- data/lib/seafoam/graal/source.rb +5 -9
- data/lib/seafoam/markdown_writer.rb +16 -0
- data/lib/seafoam/mermaid_writer.rb +40 -0
- data/lib/seafoam/passes/graal.rb +0 -2
- data/lib/seafoam/version.rb +1 -1
- data/lib/seafoam.rb +4 -2
- metadata +10 -35
- data/bin/cfg2asm +0 -20
- data/lib/seafoam/cfg/cfg_parser.rb +0 -93
- data/lib/seafoam/cfg/disassembler.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bd98e20ee30268f65b81cdee7e286133d09d3daa1a5906500e46391fb8a77e8
|
4
|
+
data.tar.gz: 19e94f385a5e0b056b0431f9385b0ca75796b368e5b767a537406c4f87f83329
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91d9279f98008fd449f4be3ecf4d45fdf2ddac3e3eb1f6eae0b0451a2dbb58d3a52b95fa12be3a805aa208ccb56033fe32b093f653331907cbc3d51b3d033f56
|
7
|
+
data.tar.gz: f2bb55b41d82f3176dcfc1bbfc46bdd01711ac8a4e61995338e5e127a196f43b40eff3ef81b2e16448dd86f4535a215d83accf7643f5f92787ff9b1d74c964b9
|
data/lib/seafoam/commands.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'set'
|
2
3
|
|
3
4
|
module Seafoam
|
4
5
|
# Implementations of the command-line commands that you can run in Seafoam.
|
@@ -10,6 +11,14 @@ module Seafoam
|
|
10
11
|
# Run the general seafoam command.
|
11
12
|
def seafoam(*args)
|
12
13
|
first, *args = args
|
14
|
+
|
15
|
+
if first == '--json'
|
16
|
+
formatter_module = Seafoam::Formatters::Json
|
17
|
+
first, *args = args
|
18
|
+
else
|
19
|
+
formatter_module = Seafoam::Formatters::Text
|
20
|
+
end
|
21
|
+
|
13
22
|
case first
|
14
23
|
when nil, 'help', '-h', '--help', '-help'
|
15
24
|
raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
|
@@ -24,21 +33,23 @@ module Seafoam
|
|
24
33
|
when nil
|
25
34
|
help(*args)
|
26
35
|
when 'info'
|
27
|
-
info name, *args
|
36
|
+
info name, formatter_module, *args
|
28
37
|
when 'list'
|
29
|
-
list name, *args
|
38
|
+
list name, formatter_module, *args
|
30
39
|
when 'search'
|
31
40
|
search name, *args
|
32
41
|
when 'edges'
|
33
|
-
edges name, *args
|
42
|
+
edges name, formatter_module, *args
|
34
43
|
when 'props'
|
35
44
|
props name, *args
|
36
45
|
when 'source'
|
37
|
-
source name, *args
|
46
|
+
source name, formatter_module, *args
|
38
47
|
when 'render'
|
39
48
|
render name, *args
|
40
49
|
when 'debug'
|
41
50
|
debug name, *args
|
51
|
+
when 'describe'
|
52
|
+
describe name, formatter_module, *args
|
42
53
|
else
|
43
54
|
raise ArgumentError, "unknown command #{command}"
|
44
55
|
end
|
@@ -136,82 +147,42 @@ module Seafoam
|
|
136
147
|
end
|
137
148
|
end
|
138
149
|
|
139
|
-
def cfg2asm(*args)
|
140
|
-
case args.first
|
141
|
-
when nil, 'help', '-h', '--help', '-help'
|
142
|
-
args = args.drop(1)
|
143
|
-
raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
|
144
|
-
|
145
|
-
@out.puts 'cfg2asm file.bgv...'
|
146
|
-
@out.puts ' --no-comments'
|
147
|
-
@out.puts ' --help'
|
148
|
-
@out.puts ' --version'
|
149
|
-
when 'version', '-v', '-version', '--version'
|
150
|
-
args = args.drop(1)
|
151
|
-
version(*args)
|
152
|
-
else
|
153
|
-
comments = true
|
154
|
-
files = []
|
155
|
-
|
156
|
-
until args.empty?
|
157
|
-
arg = args.shift
|
158
|
-
if arg.start_with?('-')
|
159
|
-
case arg
|
160
|
-
when '--no-comments'
|
161
|
-
comments = false
|
162
|
-
else
|
163
|
-
raise ArgumentError, "unknown option #{arg}"
|
164
|
-
end
|
165
|
-
else
|
166
|
-
files.push arg
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
files.each_with_index do |file, n|
|
171
|
-
parser = Seafoam::CFG::CFGParser.new(@out, file)
|
172
|
-
parser.skip_over_cfg 'After code installation'
|
173
|
-
nmethod = parser.read_nmethod
|
174
|
-
|
175
|
-
disassembler = Seafoam::CFG::Disassembler.new(@out)
|
176
|
-
@out.puts if n.positive?
|
177
|
-
@out.puts "[#{file}]"
|
178
|
-
disassembler.disassemble(nmethod, comments)
|
179
|
-
end
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
150
|
private
|
184
151
|
|
185
152
|
# seafoam file.bgv info
|
186
|
-
def info(name, *args)
|
153
|
+
def info(name, formatter_module, *args)
|
187
154
|
file, *rest = parse_name(name)
|
188
155
|
raise ArgumentError, 'info only works with a file' unless rest == [nil, nil, nil]
|
189
|
-
|
190
156
|
raise ArgumentError, 'info does not take arguments' unless args.empty?
|
191
157
|
|
192
158
|
parser = BGV::BGVParser.new(file)
|
193
159
|
major, minor = parser.read_file_header(version_check: false)
|
194
|
-
|
160
|
+
formatter = formatter_module::InfoFormatter.new(major, minor)
|
161
|
+
|
162
|
+
@out.puts formatter.format
|
195
163
|
end
|
196
164
|
|
197
165
|
# seafoam file.bgv list
|
198
|
-
def list(name, *args)
|
166
|
+
def list(name, formatter_module, *args)
|
199
167
|
file, *rest = parse_name(name)
|
200
168
|
raise ArgumentError, 'list only works with a file' unless rest == [nil, nil, nil]
|
201
|
-
|
202
169
|
raise ArgumentError, 'list does not take arguments' unless args.empty?
|
203
170
|
|
204
171
|
parser = BGV::BGVParser.new(file)
|
205
172
|
parser.read_file_header
|
206
173
|
parser.skip_document_props
|
174
|
+
entries = []
|
207
175
|
loop do
|
208
176
|
index, = parser.read_graph_preheader
|
209
177
|
break unless index
|
210
178
|
|
211
179
|
graph_header = parser.read_graph_header
|
212
|
-
|
180
|
+
entries << formatter_module::ListFormatter::Entry.new(file, parser.graph_name(graph_header), index)
|
213
181
|
parser.skip_graph
|
214
182
|
end
|
183
|
+
|
184
|
+
formatter = formatter_module::ListFormatter.new(entries)
|
185
|
+
@out.puts formatter.format
|
215
186
|
end
|
216
187
|
|
217
188
|
# seafoam file.bgv:n... search term...
|
@@ -270,12 +241,13 @@ module Seafoam
|
|
270
241
|
end
|
271
242
|
|
272
243
|
# seafoam file.bgv:n... edges
|
273
|
-
def edges(name, *args)
|
244
|
+
def edges(name, formatter_module, *args)
|
274
245
|
file, graph_index, node_id, edge_id = parse_name(name)
|
275
246
|
raise ArgumentError, 'edges needs at least a graph' unless graph_index
|
276
|
-
|
277
247
|
raise ArgumentError, 'edges does not take arguments' unless args.empty?
|
278
248
|
|
249
|
+
entry = nil
|
250
|
+
|
279
251
|
with_graph(file, graph_index) do |parser|
|
280
252
|
parser.read_graph_header
|
281
253
|
graph = parser.read_graph
|
@@ -291,24 +263,18 @@ module Seafoam
|
|
291
263
|
edges = node.outputs.select { |edge| edge.to == to }
|
292
264
|
raise ArgumentError, 'edge not found' if edges.empty?
|
293
265
|
|
294
|
-
|
295
|
-
@out.puts "#{edge.from.id_and_label} ->(#{edge.props[:label]}) #{edge.to.id_and_label}"
|
296
|
-
end
|
266
|
+
entry = formatter_module::EdgesFormatter::EdgesEntry.new(edges)
|
297
267
|
else
|
298
|
-
|
299
|
-
node.inputs.each do |input|
|
300
|
-
@out.puts " #{node.id_and_label} <-(#{input.props[:label]}) #{input.from.id_and_label}"
|
301
|
-
end
|
302
|
-
@out.puts 'Output:'
|
303
|
-
node.outputs.each do |output|
|
304
|
-
@out.puts " #{node.id_and_label} ->(#{output.props[:label]}) #{output.to.id_and_label}"
|
305
|
-
end
|
268
|
+
entry = formatter_module::EdgesFormatter::NodeEntry.new(node)
|
306
269
|
end
|
307
270
|
break
|
308
271
|
else
|
309
|
-
|
272
|
+
entry = formatter_module::EdgesFormatter::SummaryEntry.new(graph.nodes.count, graph.edges.count)
|
310
273
|
end
|
311
274
|
end
|
275
|
+
|
276
|
+
formatter = formatter_module::EdgesFormatter.new(entry)
|
277
|
+
@out.puts formatter.format
|
312
278
|
end
|
313
279
|
|
314
280
|
# seafoam file.bgv... props
|
@@ -357,7 +323,7 @@ module Seafoam
|
|
357
323
|
end
|
358
324
|
|
359
325
|
# seafoam file.bgv:n:n source
|
360
|
-
def source(name, *args)
|
326
|
+
def source(name, formatter_module, *args)
|
361
327
|
file, graph_index, node_id, edge_id = parse_name(name)
|
362
328
|
raise ArgumentError, 'source needs a node' unless node_id
|
363
329
|
raise ArgumentError, 'source only works with a node' if edge_id
|
@@ -369,7 +335,57 @@ module Seafoam
|
|
369
335
|
node = graph.nodes[node_id]
|
370
336
|
raise ArgumentError, 'node not found' unless node
|
371
337
|
|
372
|
-
|
338
|
+
formatter = formatter_module::SourceFormatter.new(node.props['nodeSourcePosition'])
|
339
|
+
@out.puts formatter.format
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# seafoam file.bgv:n describe
|
344
|
+
def describe(name, formatter_module, *args)
|
345
|
+
file, graph_index, *rest = parse_name(name)
|
346
|
+
|
347
|
+
if graph_index.nil? || !rest.all?(&:nil?)
|
348
|
+
raise ArgumentError, 'describe only works with a graph'
|
349
|
+
end
|
350
|
+
raise ArgumentError, 'describe does not take arguments' unless args.empty?
|
351
|
+
|
352
|
+
parser = BGV::BGVParser.new(file)
|
353
|
+
parser.read_file_header
|
354
|
+
parser.skip_document_props
|
355
|
+
|
356
|
+
loop do
|
357
|
+
index, = parser.read_graph_preheader
|
358
|
+
break unless index
|
359
|
+
|
360
|
+
parser.skip_graph_header
|
361
|
+
|
362
|
+
if index != graph_index
|
363
|
+
parser.skip_graph
|
364
|
+
next
|
365
|
+
end
|
366
|
+
|
367
|
+
graph = parser.read_graph
|
368
|
+
description = Seafoam::Graal::GraphDescription.new
|
369
|
+
|
370
|
+
graph.nodes.each_value do |node|
|
371
|
+
node_class = node.props.dig(:node_class, :node_class)
|
372
|
+
case node_class
|
373
|
+
when 'org.graalvm.compiler.nodes.IfNode'
|
374
|
+
description.branches = true
|
375
|
+
when 'org.graalvm.compiler.nodes.LoopBeginNode'
|
376
|
+
description.loops = true
|
377
|
+
when 'org.graalvm.compiler.nodes.InvokeNode', 'org.graalvm.compiler.nodes.InvokeWithExceptionNode'
|
378
|
+
description.calls = true
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
description.deopts = graph.nodes[0].outputs.map(&:to)
|
383
|
+
.all? { |t| t.props.dig(:node_class, :node_class) == 'org.graalvm.compiler.nodes.DeoptimizeNode' }
|
384
|
+
|
385
|
+
formatter = formatter_module::DescribeFormatter.new(graph, description)
|
386
|
+
@out.puts formatter.format
|
387
|
+
|
388
|
+
break
|
373
389
|
end
|
374
390
|
end
|
375
391
|
|
@@ -398,6 +414,28 @@ module Seafoam
|
|
398
414
|
out_file = args.shift
|
399
415
|
explicit_out_file = true
|
400
416
|
raise ArgumentError, 'no file for --out' unless out_file
|
417
|
+
|
418
|
+
out_ext = File.extname(out_file).downcase
|
419
|
+
case out_ext
|
420
|
+
when '.pdf'
|
421
|
+
out_format = :pdf
|
422
|
+
when '.svg'
|
423
|
+
out_format = :svg
|
424
|
+
when '.png'
|
425
|
+
out_format = :png
|
426
|
+
when '.dot'
|
427
|
+
out_format = :dot
|
428
|
+
when '.mmd'
|
429
|
+
out_format = :mmd
|
430
|
+
when '.md'
|
431
|
+
out_format = :md
|
432
|
+
else
|
433
|
+
raise ArgumentError, "unknown render format #{out_ext}"
|
434
|
+
end
|
435
|
+
when '--md'
|
436
|
+
out_file = @out
|
437
|
+
out_format = :md
|
438
|
+
explicit_out_file = true
|
401
439
|
when '--spotlight'
|
402
440
|
spotlight_arg = args.shift
|
403
441
|
raise ArgumentError, 'no list for --spotlight' unless spotlight_arg
|
@@ -429,20 +467,7 @@ module Seafoam
|
|
429
467
|
end
|
430
468
|
end
|
431
469
|
out_file ||= 'graph.pdf'
|
432
|
-
|
433
|
-
case out_ext
|
434
|
-
when '.pdf'
|
435
|
-
out_format = :pdf
|
436
|
-
when '.svg'
|
437
|
-
out_format = :svg
|
438
|
-
when '.png'
|
439
|
-
out_format = :png
|
440
|
-
when '.dot'
|
441
|
-
out_format = :dot
|
442
|
-
else
|
443
|
-
raise ArgumentError, "unknown render format #{out_ext}"
|
444
|
-
end
|
445
|
-
|
470
|
+
out_format ||= :pdf
|
446
471
|
with_graph(file, graph_index) do |parser|
|
447
472
|
parser.skip_graph_header
|
448
473
|
graph = parser.read_graph
|
@@ -457,10 +482,29 @@ module Seafoam
|
|
457
482
|
end
|
458
483
|
spotlight.shade
|
459
484
|
end
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
485
|
+
case out_format
|
486
|
+
when :dot, :mmd, :md
|
487
|
+
action = lambda do |stream|
|
488
|
+
case out_format
|
489
|
+
when :dot
|
490
|
+
writer = GraphvizWriter.new(stream)
|
491
|
+
writer.write_graph graph, false, draw_blocks
|
492
|
+
when :mmd
|
493
|
+
writer = MermaidWriter.new(stream)
|
494
|
+
writer.write_graph graph
|
495
|
+
when :md
|
496
|
+
writer = MarkdownWriter.new(stream)
|
497
|
+
writer.write_graph graph
|
498
|
+
else
|
499
|
+
raise
|
500
|
+
end
|
501
|
+
end
|
502
|
+
if out_file.is_a?(String)
|
503
|
+
File.open(out_file, 'w') do |stream|
|
504
|
+
action.call stream
|
505
|
+
end
|
506
|
+
else
|
507
|
+
action.call out_file
|
464
508
|
end
|
465
509
|
else
|
466
510
|
begin
|
@@ -564,6 +608,7 @@ module Seafoam
|
|
564
608
|
@out.puts ' file.bgv[:graph][:node[-edge]] edges'
|
565
609
|
@out.puts ' file.bgv[:graph][:node[-edge]] props'
|
566
610
|
@out.puts ' file.bgv:graph:node source'
|
611
|
+
@out.puts ' file.bgv:graph describe'
|
567
612
|
@out.puts ' file.bgv:graph render'
|
568
613
|
@out.puts ' --spotlight n,n,n...'
|
569
614
|
@out.puts ' --out graph.pdf'
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Seafoam
|
2
|
+
module Formatters
|
3
|
+
module Base
|
4
|
+
# Formats the output of the `describe` command.
|
5
|
+
class DescribeFormatter
|
6
|
+
attr_reader :graph, :description
|
7
|
+
|
8
|
+
def initialize(graph, description)
|
9
|
+
@graph = graph
|
10
|
+
@description = description
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Formats the output of the `edges` command.
|
15
|
+
class EdgesFormatter
|
16
|
+
EdgesEntry = Struct.new(:edges) do
|
17
|
+
def render(formatter)
|
18
|
+
formatter.render_edges_entry(edges)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
NodeEntry = Struct.new(:node) do
|
23
|
+
def render(formatter)
|
24
|
+
formatter.render_node_entry(node)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
SummaryEntry = Struct.new(:node_count, :edge_count) do
|
29
|
+
def render(formatter)
|
30
|
+
formatter.render_summary_entry(node_count, edge_count)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :entry
|
35
|
+
|
36
|
+
def initialize(entry)
|
37
|
+
@entry = entry
|
38
|
+
end
|
39
|
+
|
40
|
+
def format
|
41
|
+
entry.render(self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def render_edges_entry(edges)
|
45
|
+
raise NotImplementedError
|
46
|
+
end
|
47
|
+
|
48
|
+
def render_node_entry(node)
|
49
|
+
raise NotImplementedError
|
50
|
+
end
|
51
|
+
|
52
|
+
def render_summary_entry(node_count, edge_count)
|
53
|
+
raise NotImplementedError
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Formats the output of the `info` command.
|
58
|
+
class InfoFormatter
|
59
|
+
attr_reader :major_version, :minor_version
|
60
|
+
|
61
|
+
def initialize(major_version, minor_version)
|
62
|
+
@major_version = major_version
|
63
|
+
@minor_version = minor_version
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Formats the output of the `list` command.
|
68
|
+
class ListFormatter
|
69
|
+
Entry = Struct.new(:file, :graph_name_components, :index)
|
70
|
+
|
71
|
+
attr_reader :entries
|
72
|
+
|
73
|
+
def initialize(entries)
|
74
|
+
@entries = entries
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Formats the output of the `source` command.
|
79
|
+
class SourceFormatter
|
80
|
+
attr_reader :source_position
|
81
|
+
|
82
|
+
def initialize(source_position)
|
83
|
+
@source_position = source_position
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
module Seafoam
|
2
|
+
# Formatters are the mechanism by which `seafoam` command output is presented to the user.
|
3
|
+
module Formatters
|
4
|
+
autoload :Base, 'seafoam/formatters/base'
|
5
|
+
autoload :Json, 'seafoam/formatters/json'
|
6
|
+
autoload :Text, 'seafoam/formatters/text'
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Seafoam
|
4
|
+
module Formatters
|
5
|
+
module Json
|
6
|
+
# A JSON-based formatter for the `describe` command.
|
7
|
+
class DescribeFormatter < Seafoam::Formatters::Base::DescribeFormatter
|
8
|
+
def format
|
9
|
+
ret = Seafoam::Graal::GraphDescription::ATTRIBUTES.map { |attr| [attr, description.send(attr)] }.to_h
|
10
|
+
ret[:node_count] = graph.nodes.size
|
11
|
+
|
12
|
+
ret.to_json
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# A JSON-based formatter for the `edges` command.
|
17
|
+
class EdgesFormatter < Seafoam::Formatters::Base::EdgesFormatter
|
18
|
+
def render_edges_entry(edges)
|
19
|
+
edges.map { |edge| build_edge(edge) }.to_json
|
20
|
+
end
|
21
|
+
|
22
|
+
def render_node_entry(node)
|
23
|
+
{
|
24
|
+
input: node.inputs.map { |input| build_edge(input) },
|
25
|
+
output: node.outputs.map { |output| build_edge(output) }
|
26
|
+
}.to_json
|
27
|
+
end
|
28
|
+
|
29
|
+
def render_summary_entry(node_count, edge_count)
|
30
|
+
{ node_count: node_count, edge_count: edge_count }.to_json
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_node(node)
|
34
|
+
{ id: node.id.to_s, label: node.props[:label] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_edge(edge)
|
38
|
+
{ from: build_node(edge.from), to: build_node(edge.to), label: edge.props[:label] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# A JSON-based formatter for the `info` command.
|
43
|
+
class InfoFormatter < Seafoam::Formatters::Base::InfoFormatter
|
44
|
+
def format
|
45
|
+
{
|
46
|
+
major_version: major_version,
|
47
|
+
minor_version: minor_version
|
48
|
+
}.to_json
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# A JSON-based formatter for the `list` command.
|
53
|
+
class ListFormatter < Seafoam::Formatters::Base::ListFormatter
|
54
|
+
def format
|
55
|
+
entries.map do |entry|
|
56
|
+
{
|
57
|
+
graph_index: entry.index,
|
58
|
+
graph_file: entry.file,
|
59
|
+
graph_name_components: entry.graph_name_components
|
60
|
+
}
|
61
|
+
end.to_json
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# A JSON-based formatter for the `source` command.
|
66
|
+
class SourceFormatter < Seafoam::Formatters::Base::SourceFormatter
|
67
|
+
def format
|
68
|
+
Seafoam::Graal::Source.walk(source_position, &method(:render_method)).to_json
|
69
|
+
end
|
70
|
+
|
71
|
+
def render_method(method)
|
72
|
+
declaring_class = method[:declaring_class]
|
73
|
+
name = method[:method_name]
|
74
|
+
{
|
75
|
+
class: declaring_class,
|
76
|
+
method: name
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Seafoam
|
2
|
+
module Formatters
|
3
|
+
module Text
|
4
|
+
# A plain-text formatter for the `describe` command.
|
5
|
+
class DescribeFormatter < Seafoam::Formatters::Base::DescribeFormatter
|
6
|
+
def format
|
7
|
+
notes = Seafoam::Graal::GraphDescription::ATTRIBUTES.select { |attr| description.send(attr) }
|
8
|
+
|
9
|
+
["#{graph.nodes.size} nodes", *notes].join(', ')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# A plain-text formatter for the `edges` command.
|
14
|
+
class EdgesFormatter < Seafoam::Formatters::Base::EdgesFormatter
|
15
|
+
def render_edges_entry(edges)
|
16
|
+
edges.map do |edge|
|
17
|
+
"#{edge.from.id_and_label} ->(#{edge.props[:label]}) #{edge.to.id_and_label}"
|
18
|
+
end.join("\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
def render_node_entry(node)
|
22
|
+
ret = ['Input:']
|
23
|
+
ret += node.inputs.map do |input|
|
24
|
+
" #{node.id_and_label} <-(#{input.props[:label]}) #{input.from.id_and_label}"
|
25
|
+
end
|
26
|
+
|
27
|
+
ret << 'Output:'
|
28
|
+
ret += node.outputs.map do |output|
|
29
|
+
" #{node.id_and_label} ->(#{output.props[:label]}) #{output.to.id_and_label}"
|
30
|
+
end
|
31
|
+
|
32
|
+
ret.join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
def render_summary_entry(node_count, edge_count)
|
36
|
+
"#{node_count} nodes, #{edge_count} edges"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# A plain-text formatter for the `info` command.
|
41
|
+
class InfoFormatter < Seafoam::Formatters::Base::InfoFormatter
|
42
|
+
def format
|
43
|
+
"BGV #{major_version}.#{minor_version}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# A plain-text formatter for the `list` command.
|
48
|
+
class ListFormatter < Seafoam::Formatters::Base::ListFormatter
|
49
|
+
def format
|
50
|
+
entries.map do |entry|
|
51
|
+
"#{entry.file}:#{entry.index} #{entry.graph_name_components.join('/')}"
|
52
|
+
end.join("\n")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# A plain-text formatter for the `source` command.
|
57
|
+
class SourceFormatter < Seafoam::Formatters::Base::SourceFormatter
|
58
|
+
def format
|
59
|
+
Seafoam::Graal::Source.walk(source_position, &method(:render_method)).join("\n")
|
60
|
+
end
|
61
|
+
|
62
|
+
def render_method(method)
|
63
|
+
declaring_class = method[:declaring_class]
|
64
|
+
name = method[:method_name]
|
65
|
+
"#{declaring_class}##{name}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Seafoam
|
2
|
+
module Graal
|
3
|
+
# Provides a high level description of a Graal graph's features.
|
4
|
+
class GraphDescription
|
5
|
+
ATTRIBUTES = %i[branches calls deopts linear loops]
|
6
|
+
|
7
|
+
ATTRIBUTES.each { |attr| attr_accessor(attr) unless attr == :linear }
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@branches = false
|
11
|
+
@calls = false
|
12
|
+
@deopts = false
|
13
|
+
@loops = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def linear
|
17
|
+
!branches && !loops
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/seafoam/graal/source.rb
CHANGED
@@ -2,21 +2,17 @@ module Seafoam
|
|
2
2
|
module Graal
|
3
3
|
# Routines for understanding source positions in Graal.
|
4
4
|
module Source
|
5
|
-
def self.
|
6
|
-
|
5
|
+
def self.walk(source_position, &block)
|
6
|
+
results = []
|
7
|
+
|
7
8
|
caller = source_position
|
8
9
|
while caller
|
9
10
|
method = caller[:method]
|
10
|
-
|
11
|
+
results.push block.call(method)
|
11
12
|
caller = caller[:caller]
|
12
13
|
end
|
13
|
-
lines.join("\n")
|
14
|
-
end
|
15
14
|
|
16
|
-
|
17
|
-
declaring_class = method[:declaring_class]
|
18
|
-
name = method[:method_name]
|
19
|
-
"#{declaring_class}##{name}"
|
15
|
+
results
|
20
16
|
end
|
21
17
|
end
|
22
18
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Seafoam
|
2
|
+
# A writer from graphs to Markdown format, with an embedded Mermaid graph.
|
3
|
+
class MarkdownWriter
|
4
|
+
def initialize(stream)
|
5
|
+
@stream = stream
|
6
|
+
end
|
7
|
+
|
8
|
+
# Write a graph.
|
9
|
+
def write_graph(graph)
|
10
|
+
@stream.puts '```mermaid'
|
11
|
+
mermaid = MermaidWriter.new(@stream)
|
12
|
+
mermaid.write_graph graph
|
13
|
+
@stream.puts '```'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Seafoam
|
2
|
+
# A writer from graphs to the Mermaid format.
|
3
|
+
class MermaidWriter
|
4
|
+
def initialize(stream)
|
5
|
+
@stream = stream
|
6
|
+
end
|
7
|
+
|
8
|
+
# Write a graph.
|
9
|
+
def write_graph(graph)
|
10
|
+
@stream.puts 'flowchart TD'
|
11
|
+
write_nodes graph
|
12
|
+
write_edges graph
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
# Write node declarations.
|
18
|
+
def write_nodes(graph)
|
19
|
+
graph.nodes.each_value do |node|
|
20
|
+
write_node node
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def write_node(node)
|
25
|
+
@stream.puts " node#{node.id}[#{node.props[:label].inspect}]"
|
26
|
+
end
|
27
|
+
|
28
|
+
# Write edge declarations.
|
29
|
+
|
30
|
+
def write_edges(graph)
|
31
|
+
graph.edges.each do |edge|
|
32
|
+
write_edge edge
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_edge(edge)
|
37
|
+
@stream.puts " node#{edge.from.id} --> node#{edge.to.id}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/seafoam/passes/graal.rb
CHANGED
@@ -262,8 +262,6 @@ module Seafoam
|
|
262
262
|
# loopBegin edges point from LoopEndNode (continue) and LoopExitNode
|
263
263
|
# (break) to the LoopBeginNode. Both are drawn reversed.
|
264
264
|
when 'loopBegin'
|
265
|
-
edge.props[:hidden] = true
|
266
|
-
|
267
265
|
case edge.to.props.dig(:node_class, :node_class)
|
268
266
|
when 'org.graalvm.compiler.nodes.LoopEndNode'
|
269
267
|
# If it's from the LoopEnd then it's the control edge to follow.
|
data/lib/seafoam/version.rb
CHANGED
data/lib/seafoam.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
require 'seafoam/version'
|
2
2
|
require 'seafoam/binary/io_binary_reader'
|
3
3
|
require 'seafoam/bgv/bgv_parser'
|
4
|
-
require 'seafoam/cfg/cfg_parser'
|
5
|
-
require 'seafoam/cfg/disassembler'
|
6
4
|
require 'seafoam/colors'
|
7
5
|
require 'seafoam/graph'
|
6
|
+
require 'seafoam/graal/graph_description'
|
8
7
|
require 'seafoam/graal/source'
|
9
8
|
require 'seafoam/graal/pi'
|
10
9
|
require 'seafoam/passes'
|
@@ -15,4 +14,7 @@ require 'seafoam/spotlight'
|
|
15
14
|
require 'seafoam/isabelle_writer'
|
16
15
|
require 'seafoam/json_writer'
|
17
16
|
require 'seafoam/graphviz_writer'
|
17
|
+
require 'seafoam/mermaid_writer'
|
18
|
+
require 'seafoam/markdown_writer'
|
18
19
|
require 'seafoam/commands'
|
20
|
+
require 'seafoam/formatters/formatters'
|
metadata
CHANGED
@@ -1,43 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seafoam
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.13'
|
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: 2022-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: crabstone
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '4.0'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '4.0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: benchmark-ips
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '2.7'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '2.7'
|
41
13
|
- !ruby/object:Gem::Dependency
|
42
14
|
name: rake
|
43
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -86,27 +58,30 @@ executables:
|
|
86
58
|
- seafoam
|
87
59
|
- bgv2json
|
88
60
|
- bgv2isabelle
|
89
|
-
- cfg2asm
|
90
61
|
extensions: []
|
91
62
|
extra_rdoc_files: []
|
92
63
|
files:
|
93
64
|
- bin/bgv2isabelle
|
94
65
|
- bin/bgv2json
|
95
|
-
- bin/cfg2asm
|
96
66
|
- bin/seafoam
|
97
67
|
- lib/seafoam.rb
|
98
68
|
- lib/seafoam/bgv/bgv_parser.rb
|
99
69
|
- lib/seafoam/binary/io_binary_reader.rb
|
100
|
-
- lib/seafoam/cfg/cfg_parser.rb
|
101
|
-
- lib/seafoam/cfg/disassembler.rb
|
102
70
|
- lib/seafoam/colors.rb
|
103
71
|
- lib/seafoam/commands.rb
|
72
|
+
- lib/seafoam/formatters/base.rb
|
73
|
+
- lib/seafoam/formatters/formatters.rb
|
74
|
+
- lib/seafoam/formatters/json.rb
|
75
|
+
- lib/seafoam/formatters/text.rb
|
76
|
+
- lib/seafoam/graal/graph_description.rb
|
104
77
|
- lib/seafoam/graal/pi.rb
|
105
78
|
- lib/seafoam/graal/source.rb
|
106
79
|
- lib/seafoam/graph.rb
|
107
80
|
- lib/seafoam/graphviz_writer.rb
|
108
81
|
- lib/seafoam/isabelle_writer.rb
|
109
82
|
- lib/seafoam/json_writer.rb
|
83
|
+
- lib/seafoam/markdown_writer.rb
|
84
|
+
- lib/seafoam/mermaid_writer.rb
|
110
85
|
- lib/seafoam/passes.rb
|
111
86
|
- lib/seafoam/passes/fallback.rb
|
112
87
|
- lib/seafoam/passes/graal.rb
|
@@ -132,7 +107,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
107
|
- !ruby/object:Gem::Version
|
133
108
|
version: '0'
|
134
109
|
requirements: []
|
135
|
-
rubygems_version: 3.
|
110
|
+
rubygems_version: 3.3.3
|
136
111
|
signing_key:
|
137
112
|
specification_version: 4
|
138
113
|
summary: A tool for working with compiler graphs
|
data/bin/cfg2asm
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'seafoam'
|
4
|
-
|
5
|
-
# This is the 'cfg2asm' command line entry point.
|
6
|
-
|
7
|
-
begin
|
8
|
-
# Run the command line.
|
9
|
-
commands = Seafoam::Commands.new($stdout)
|
10
|
-
commands.cfg2asm(*ARGV)
|
11
|
-
rescue StandardError => e
|
12
|
-
if $DEBUG
|
13
|
-
# Re-raise the exception so the user sees it, if debugging is
|
14
|
-
# enabled (ruby -d).
|
15
|
-
raise e
|
16
|
-
else
|
17
|
-
# Otherwise, just print the message.
|
18
|
-
warn "seafoam: #{e.message}"
|
19
|
-
end
|
20
|
-
end
|
@@ -1,93 +0,0 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
require 'zlib'
|
3
|
-
|
4
|
-
module Seafoam
|
5
|
-
module CFG
|
6
|
-
Code = Struct.new(:arch, :arch_width, :base, :code)
|
7
|
-
Comment = Struct.new(:offset, :comment)
|
8
|
-
NMethod = Struct.new(:code, :comments)
|
9
|
-
|
10
|
-
# A parser for CFG files.
|
11
|
-
class CFGParser
|
12
|
-
def initialize(out, file)
|
13
|
-
@out = out
|
14
|
-
data = File.read(file, encoding: Encoding::ASCII_8BIT)
|
15
|
-
if data[0..1].bytes == [0x1f, 0x8b]
|
16
|
-
data = Zlib.gunzip(data)
|
17
|
-
end
|
18
|
-
@reader = StringIO.new(data)
|
19
|
-
@state = :any
|
20
|
-
@cfg_name = nil
|
21
|
-
end
|
22
|
-
|
23
|
-
def skip_over_cfg(name)
|
24
|
-
loop do
|
25
|
-
line = @reader.readline("\n")
|
26
|
-
case line
|
27
|
-
when "begin_cfg\n"
|
28
|
-
@state = :cfg
|
29
|
-
@cfg_name = nil
|
30
|
-
when / name "(.*)"\n/
|
31
|
-
if @state == :cfg
|
32
|
-
@cfg_name = Regexp.last_match(1)
|
33
|
-
end
|
34
|
-
when "end_cfg\n"
|
35
|
-
raise unless @state == :cfg
|
36
|
-
|
37
|
-
@state = :any
|
38
|
-
break if @cfg_name == name
|
39
|
-
else
|
40
|
-
next
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def read_nmethod
|
46
|
-
raise unless @state == :any
|
47
|
-
|
48
|
-
arch = nil
|
49
|
-
arch_width = nil
|
50
|
-
code = nil
|
51
|
-
comments = []
|
52
|
-
raise unless @reader.readline == "begin_nmethod\n"
|
53
|
-
|
54
|
-
loop do
|
55
|
-
line = @reader.readline("\n")
|
56
|
-
case line
|
57
|
-
when / Platform (.*) (.*) <\|\|@\n/
|
58
|
-
arch = Regexp.last_match(1)
|
59
|
-
arch_width = Regexp.last_match(2)
|
60
|
-
when / HexCode (.*) (.*) <\|\|@\n/
|
61
|
-
base = Regexp.last_match(1).to_i(16)
|
62
|
-
code = [Regexp.last_match(2)].pack('H*')
|
63
|
-
raise if arch.nil? || arch_width.nil?
|
64
|
-
|
65
|
-
code = Code.new(arch, arch_width, base, code)
|
66
|
-
when / Comment (\d*) (.*) <\|\|@\n/
|
67
|
-
offset = Regexp.last_match(1).to_i
|
68
|
-
comment = Regexp.last_match(2)
|
69
|
-
comments.push Comment.new(offset, comment)
|
70
|
-
when " <<<HexCodeFile\n"
|
71
|
-
next
|
72
|
-
when " HexCodeFile>>> <|@\n"
|
73
|
-
next
|
74
|
-
when "end_nmethod\n"
|
75
|
-
break
|
76
|
-
when / (.*) <\|\|@\n/
|
77
|
-
offset = -1
|
78
|
-
comment = Regexp.last_match(1)
|
79
|
-
comments.push Comment.new(offset, comment)
|
80
|
-
when / (.*)\n/
|
81
|
-
offset = -1
|
82
|
-
comment = Regexp.last_match(1)
|
83
|
-
comments.push Comment.new(offset, comment)
|
84
|
-
else
|
85
|
-
# In case anything was missed
|
86
|
-
raise 'There is currently no case for this line. Please open an issue so it can be addressed.'
|
87
|
-
end
|
88
|
-
end
|
89
|
-
NMethod.new(code, comments)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
|
3
|
-
module Seafoam
|
4
|
-
module CFG
|
5
|
-
# Disassemble and print comments from cfg files
|
6
|
-
class Disassembler
|
7
|
-
def initialize(out)
|
8
|
-
@out = out
|
9
|
-
end
|
10
|
-
|
11
|
-
def disassemble(nmethod, print_comments)
|
12
|
-
require_crabstone
|
13
|
-
|
14
|
-
comments = nmethod.comments
|
15
|
-
comments_n = 0
|
16
|
-
|
17
|
-
case [nmethod.code.arch, nmethod.code.arch_width]
|
18
|
-
when %w[AMD64 64]
|
19
|
-
crabstone_arch = [Crabstone::ARCH_X86, Crabstone::MODE_64]
|
20
|
-
else
|
21
|
-
raise "Unknown architecture #{nmethod.code.arch} and bit width #{nmethod.code.arch_width}"
|
22
|
-
end
|
23
|
-
|
24
|
-
cs = Crabstone::Disassembler.new(*crabstone_arch)
|
25
|
-
begin
|
26
|
-
cs.disasm(nmethod.code.code, nmethod.code.base).each do |i|
|
27
|
-
if print_comments
|
28
|
-
# Print comments associated to the instruction.
|
29
|
-
last_comment = i.address + i.bytes.length - nmethod.code.base
|
30
|
-
while comments_n < comments.length && comments[comments_n].offset < last_comment
|
31
|
-
if comments[comments_n].offset == -1
|
32
|
-
@out.printf("\t\t\t\t;%<comment>s\n", comment: comments[comments_n].comment)
|
33
|
-
else
|
34
|
-
@out.printf(
|
35
|
-
"\t\t\t\t;Comment %<loc>i:\t%<comment>s\n",
|
36
|
-
loc: comments[comments_n].offset,
|
37
|
-
comment: comments[comments_n].comment
|
38
|
-
)
|
39
|
-
end
|
40
|
-
comments_n += 1
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
# Print the instruction.
|
45
|
-
@out.printf(
|
46
|
-
"\t0x%<address>x:\t%<instruction>s\t%<details>s\n",
|
47
|
-
address: i.address,
|
48
|
-
instruction: i.mnemonic,
|
49
|
-
details: i.op_str
|
50
|
-
)
|
51
|
-
end
|
52
|
-
rescue StandardError => e
|
53
|
-
raise "Disassembly error: #{e.message}"
|
54
|
-
ensure
|
55
|
-
cs.close
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def require_crabstone
|
60
|
-
require 'crabstone'
|
61
|
-
rescue LoadError => e
|
62
|
-
if $DEBUG
|
63
|
-
raise e
|
64
|
-
else
|
65
|
-
raise 'Could not load Capstone - is it installed?'
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|