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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 49d090779b04322f8c82aae0cf11ea2e39cd4cef1f3d9dc2b51de1f27e565f9c
4
- data.tar.gz: 121f1c7bb327c30f1a388960012c43dab49e0f1079d5a420811ab2332de9663b
3
+ metadata.gz: 3bd98e20ee30268f65b81cdee7e286133d09d3daa1a5906500e46391fb8a77e8
4
+ data.tar.gz: 19e94f385a5e0b056b0431f9385b0ca75796b368e5b767a537406c4f87f83329
5
5
  SHA512:
6
- metadata.gz: dd13ee2efe4fe4118fc5f6bdb56c55b3c1c5f28b86a3d37ce3cf4fc72bcdcbeb161968260b62626047b9f9f9eedec53cbc743d79512a11bd77b195f054c54262
7
- data.tar.gz: 67e06a4e89fdf9e6297e3d68da7f7b0e7d4f1de95a97c9be17c12e6c9114476ee6e747c6306c3bc472e549317f20c19c315c9789d1814c68fdd85501327c432b
6
+ metadata.gz: 91d9279f98008fd449f4be3ecf4d45fdf2ddac3e3eb1f6eae0b0451a2dbb58d3a52b95fa12be3a805aa208ccb56033fe32b093f653331907cbc3d51b3d033f56
7
+ data.tar.gz: f2bb55b41d82f3176dcfc1bbfc46bdd01711ac8a4e61995338e5e127a196f43b40eff3ef81b2e16448dd86f4535a215d83accf7643f5f92787ff9b1d74c964b9
@@ -162,8 +162,7 @@ module Seafoam
162
162
  count += 1
163
163
  arg
164
164
  end
165
- components = groups_names + [name]
166
- components.join('/')
165
+ groups_names + [name]
167
166
  end
168
167
 
169
168
  private
@@ -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
- @out.puts "BGV #{major}.#{minor}"
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
- @out.puts "#{file}:#{index} #{parser.graph_name(graph_header)}"
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
- edges.each do |edge|
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
- @out.puts 'Input:'
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
- @out.puts "#{graph.nodes.count} nodes, #{graph.edges.count} edges"
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
- @out.puts Graal::Source.render(node.props['nodeSourcePosition'])
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
- out_ext = File.extname(out_file).downcase
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
- if out_format == :dot
461
- File.open(out_file, 'w') do |stream|
462
- writer = GraphvizWriter.new(stream)
463
- writer.write_graph graph, false, draw_blocks
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
@@ -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.render(source_position)
6
- lines = []
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
- lines.push render_method(method)
11
+ results.push block.call(method)
11
12
  caller = caller[:caller]
12
13
  end
13
- lines.join("\n")
14
- end
15
14
 
16
- def self.render_method(method)
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
@@ -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.
@@ -1,5 +1,5 @@
1
1
  module Seafoam
2
2
  MAJOR_VERSION = 0
3
- MINOR_VERSION = 9
3
+ MINOR_VERSION = 13
4
4
  VERSION = "#{MAJOR_VERSION}.#{MINOR_VERSION}"
5
5
  end
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.9'
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: 2021-09-27 00:00:00.000000000 Z
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.2.22
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