seafoam 0.9 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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