seafoam 0.5 → 0.9

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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/bin/bgv2isabelle +1 -5
  3. data/bin/bgv2json +1 -5
  4. data/bin/cfg2asm +1 -5
  5. data/bin/seafoam +1 -5
  6. data/lib/seafoam/bgv/bgv_parser.rb +10 -2
  7. data/lib/seafoam/commands.rb +53 -32
  8. data/lib/seafoam/graal/pi.rb +18 -0
  9. data/lib/seafoam/graph.rb +33 -1
  10. data/lib/seafoam/graphviz_writer.rb +24 -2
  11. data/lib/seafoam/json_writer.rb +1 -3
  12. data/lib/seafoam/{annotators → passes}/fallback.rb +4 -4
  13. data/lib/seafoam/{annotators → passes}/graal.rb +43 -13
  14. data/lib/seafoam/passes/truffle.rb +58 -0
  15. data/lib/seafoam/passes.rb +61 -0
  16. data/lib/seafoam/version.rb +1 -1
  17. data/lib/seafoam.rb +5 -4
  18. metadata +32 -66
  19. data/.github/probots.yml +0 -2
  20. data/.github/workflows/workflows.yml +0 -39
  21. data/.gitignore +0 -7
  22. data/.rubocop.yml +0 -37
  23. data/.ruby-version +0 -1
  24. data/.seafoam/config +0 -1
  25. data/CODE_OF_CONDUCT.md +0 -128
  26. data/CONTRIBUTING.md +0 -5
  27. data/Gemfile +0 -2
  28. data/LICENSE.md +0 -7
  29. data/README.md +0 -378
  30. data/demos/box-unbox-stats +0 -65
  31. data/docs/annotators.md +0 -43
  32. data/docs/bgv.md +0 -293
  33. data/docs/getting-graphs.md +0 -59
  34. data/docs/images/igv.png +0 -0
  35. data/docs/images/seafoam.png +0 -0
  36. data/docs/images/spotlight-igv.png +0 -0
  37. data/docs/images/spotlight-seafoam.png +0 -0
  38. data/docs/json.md +0 -35
  39. data/examples/Fib.java +0 -24
  40. data/examples/MatMult.java +0 -39
  41. data/examples/fib-java.bgv.gz +0 -0
  42. data/examples/fib.js +0 -15
  43. data/examples/fib.rb +0 -15
  44. data/examples/identity.rb +0 -13
  45. data/examples/java/Irreducible.class +0 -0
  46. data/examples/java/Irreducible.j +0 -35
  47. data/examples/java/IrreducibleDecompiled.java +0 -21
  48. data/examples/java/JavaExamples.java +0 -418
  49. data/examples/matmult.rb +0 -29
  50. data/examples/overflow.rb +0 -13
  51. data/examples/ruby/clamps.rb +0 -20
  52. data/examples/ruby/graal.patch +0 -15
  53. data/examples/ruby/ruby_examples.rb +0 -278
  54. data/lib/seafoam/annotators.rb +0 -54
  55. data/lib/seafoam/config.rb +0 -34
  56. data/seafoam.gemspec +0 -21
  57. data/spec/seafoam/annotators/fallback_spec.rb +0 -69
  58. data/spec/seafoam/annotators/graal_spec.rb +0 -96
  59. data/spec/seafoam/annotators_spec.rb +0 -61
  60. data/spec/seafoam/bgv/bgv_parser_spec.rb +0 -157
  61. data/spec/seafoam/binary/io_binary_reader_spec.rb +0 -176
  62. data/spec/seafoam/cfg/cfg_parser_spec.rb +0 -21
  63. data/spec/seafoam/cfg/disassembler_spec.rb +0 -32
  64. data/spec/seafoam/command_spec.rb +0 -316
  65. data/spec/seafoam/graph_spec.rb +0 -172
  66. data/spec/seafoam/graphviz_writer_spec.rb +0 -63
  67. data/spec/seafoam/json_writer_spec.rb +0 -14
  68. data/spec/seafoam/spec_helpers.rb +0 -34
  69. data/spec/seafoam/spotlight_spec.rb +0 -38
  70. data/tools/render-all +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '08ef3412a15721b9bfd6184645579568626e2079ff1aaaeb9d9181270c25209f'
4
- data.tar.gz: a139c2adb73d4912131b886a61482d2c67ee2090f1117a51669cfe334202ea37
3
+ metadata.gz: 49d090779b04322f8c82aae0cf11ea2e39cd4cef1f3d9dc2b51de1f27e565f9c
4
+ data.tar.gz: 121f1c7bb327c30f1a388960012c43dab49e0f1079d5a420811ab2332de9663b
5
5
  SHA512:
6
- metadata.gz: 1939c6d23e19bb930475a33c7b1fd690c2e3c57b6fcfaed3eafd3c01d4c2e2775398f796f6a22c62012e6133a27dc4575df0f3afc96785efd1fdfbd3a8203203
7
- data.tar.gz: 9fb0c9552c3df825ab9dde3b76480d08a4ef5587ccb85f1e9024e0fff85fd71b8707bd0d7cecccde69d35a6662e736ee1fbeb332f95745ca8e34a40d260da702
6
+ metadata.gz: dd13ee2efe4fe4118fc5f6bdb56c55b3c1c5f28b86a3d37ce3cf4fc72bcdcbeb161968260b62626047b9f9f9eedec53cbc743d79512a11bd77b195f054c54262
7
+ data.tar.gz: 67e06a4e89fdf9e6297e3d68da7f7b0e7d4f1de95a97c9be17c12e6c9114476ee6e747c6306c3bc472e549317f20c19c315c9789d1814c68fdd85501327c432b
data/bin/bgv2isabelle CHANGED
@@ -5,12 +5,8 @@ require 'seafoam'
5
5
  # This is the 'bgv2isabelle' command line entry point.
6
6
 
7
7
  begin
8
- # Load configuraiton.
9
- config = Seafoam::Config.new
10
- config.load_config
11
-
12
8
  # Run the command line.
13
- commands = Seafoam::Commands.new($stdout, config)
9
+ commands = Seafoam::Commands.new($stdout)
14
10
  commands.bgv2isabelle(*ARGV)
15
11
  rescue StandardError => e
16
12
  if $DEBUG
data/bin/bgv2json CHANGED
@@ -5,12 +5,8 @@ require 'seafoam'
5
5
  # This is the 'bgv2json' command line entry point.
6
6
 
7
7
  begin
8
- # Load configuraiton.
9
- config = Seafoam::Config.new
10
- config.load_config
11
-
12
8
  # Run the command line.
13
- commands = Seafoam::Commands.new($stdout, config)
9
+ commands = Seafoam::Commands.new($stdout)
14
10
  commands.bgv2json(*ARGV)
15
11
  rescue StandardError => e
16
12
  if $DEBUG
data/bin/cfg2asm CHANGED
@@ -5,12 +5,8 @@ require 'seafoam'
5
5
  # This is the 'cfg2asm' command line entry point.
6
6
 
7
7
  begin
8
- # Load configuraiton.
9
- config = Seafoam::Config.new
10
- config.load_config
11
-
12
8
  # Run the command line.
13
- commands = Seafoam::Commands.new($stdout, config)
9
+ commands = Seafoam::Commands.new($stdout)
14
10
  commands.cfg2asm(*ARGV)
15
11
  rescue StandardError => e
16
12
  if $DEBUG
data/bin/seafoam CHANGED
@@ -5,12 +5,8 @@ require 'seafoam'
5
5
  # This is the 'seafoam' command line entry point.
6
6
 
7
7
  begin
8
- # Load configuraiton.
9
- config = Seafoam::Config.new
10
- config.load_config
11
-
12
8
  # Run the command line.
13
- commands = Seafoam::Commands.new($stdout, config)
9
+ commands = Seafoam::Commands.new($stdout)
14
10
  commands.seafoam(*ARGV)
15
11
  rescue StandardError => e
16
12
  if $DEBUG
@@ -127,13 +127,21 @@ module Seafoam
127
127
  end
128
128
  end
129
129
  end
130
- skip_blocks
130
+
131
+ # Read block information.
132
+ @reader.read_sint32.times do
133
+ block_id = @reader.read_sint32
134
+ block_nodes = @reader.read_sint32.times.map { @reader.read_sint32 }
135
+ # Followers aren't used but could be.
136
+ @reader.read_sint32.times.map { @reader.read_sint32 }
137
+ graph.create_block block_id, block_nodes
138
+ end
131
139
  graph
132
140
  end
133
141
 
134
142
  # Skip over a graph, having read or skipped its headers.
135
143
  def skip_graph
136
- # Already read BEGIN_GRAPH, id, format, args, and props
144
+ # Already read BEGIN_GRAPH, id, format, args, and props.
137
145
  @reader.read_sint32.times do
138
146
  @reader.skip_int32
139
147
  node_class = read_pool_object
@@ -3,9 +3,8 @@ require 'json'
3
3
  module Seafoam
4
4
  # Implementations of the command-line commands that you can run in Seafoam.
5
5
  class Commands
6
- def initialize(out, config)
6
+ def initialize(out)
7
7
  @out = out
8
- @config = config
9
8
  end
10
9
 
11
10
  # Run the general seafoam command.
@@ -15,24 +14,7 @@ module Seafoam
15
14
  when nil, 'help', '-h', '--help', '-help'
16
15
  raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
17
16
 
18
- @out.puts 'seafoam file.bgv info'
19
- @out.puts ' file.bgv list'
20
- @out.puts ' file.bgv[:graph][:node[-edge]] search term...'
21
- @out.puts ' file.bgv[:graph][:node[-edge]] edges'
22
- @out.puts ' file.bgv[:graph][:node[-edge]] props'
23
- @out.puts ' file.bgv:graph:node source'
24
- @out.puts ' file.bgv:graph render'
25
- @out.puts ' --spotlight n,n,n...'
26
- @out.puts ' --out graph.pdf'
27
- @out.puts ' graph.svg'
28
- @out.puts ' graph.png'
29
- @out.puts ' graph.dot'
30
- @out.puts ' --show-frame-state'
31
- @out.puts ' --hide-floating'
32
- @out.puts ' --no-reduce-edges'
33
- @out.puts ' --option key value'
34
- @out.puts ' --help'
35
- @out.puts ' --version'
17
+ help(*args)
36
18
  when 'version', '-v', '-version', '--version'
37
19
  version(*args)
38
20
  else
@@ -262,7 +244,7 @@ module Seafoam
262
244
  end
263
245
 
264
246
  def search_object(tag, object, terms)
265
- full_text = JSON.generate(object)
247
+ full_text = JSON.generate(JSONWriter.prepare_json(object))
266
248
  full_text_down = full_text.downcase
267
249
  start = 0
268
250
  terms.each do |t|
@@ -298,7 +280,7 @@ module Seafoam
298
280
  parser.read_graph_header
299
281
  graph = parser.read_graph
300
282
  if node_id
301
- Annotators.apply graph
283
+ Passes.apply graph
302
284
  node = graph.nodes[node_id]
303
285
  raise ArgumentError, 'node not found' unless node
304
286
 
@@ -397,8 +379,10 @@ module Seafoam
397
379
  raise ArgumentError, 'render needs at least a graph' unless graph_index
398
380
  raise ArgumentError, 'render only works with a graph' unless rest == [nil, nil]
399
381
 
400
- annotator_options = {
382
+ pass_options = {
383
+ simplify_truffle_args: true,
401
384
  hide_frame_state: true,
385
+ hide_pi: true,
402
386
  hide_floating: false,
403
387
  reduce_edges: true
404
388
  }
@@ -406,6 +390,7 @@ module Seafoam
406
390
  args = args.dup
407
391
  out_file = nil
408
392
  explicit_out_file = false
393
+ draw_blocks = false
409
394
  until args.empty?
410
395
  arg = args.shift
411
396
  case arg
@@ -418,12 +403,18 @@ module Seafoam
418
403
  raise ArgumentError, 'no list for --spotlight' unless spotlight_arg
419
404
 
420
405
  spotlight_nodes = spotlight_arg.split(',').map { |n| Integer(n) }
406
+ when '--full-truffle-args'
407
+ pass_options[:simplify_truffle_args] = false
421
408
  when '--show-frame-state'
422
- annotator_options[:hide_frame_state] = false
409
+ pass_options[:hide_frame_state] = false
410
+ when '--show-pi'
411
+ pass_options[:hide_pi] = false
423
412
  when '--hide-floating'
424
- annotator_options[:hide_floating] = true
413
+ pass_options[:hide_floating] = true
425
414
  when '--no-reduce-edges'
426
- annotator_options[:reduce_edges] = false
415
+ pass_options[:reduce_edges] = false
416
+ when '--draw-blocks'
417
+ draw_blocks = true
427
418
  when '--option'
428
419
  key = args.shift
429
420
  raise ArgumentError, 'no key for --option' unless key
@@ -432,7 +423,7 @@ module Seafoam
432
423
  raise ArgumentError, "no value for --option #{key}" unless out_file
433
424
 
434
425
  value = { 'true' => true, 'false' => 'false' }.fetch(key, value)
435
- annotator_options[key.to_sym] = value
426
+ pass_options[key.to_sym] = value
436
427
  else
437
428
  raise ArgumentError, "unexpected option #{arg}"
438
429
  end
@@ -455,7 +446,7 @@ module Seafoam
455
446
  with_graph(file, graph_index) do |parser|
456
447
  parser.skip_graph_header
457
448
  graph = parser.read_graph
458
- Annotators.apply graph, annotator_options
449
+ Passes.apply graph, pass_options
459
450
  if spotlight_nodes
460
451
  spotlight = Spotlight.new(graph)
461
452
  spotlight_nodes.each do |node_id|
@@ -469,14 +460,14 @@ module Seafoam
469
460
  if out_format == :dot
470
461
  File.open(out_file, 'w') do |stream|
471
462
  writer = GraphvizWriter.new(stream)
472
- writer.write_graph graph
463
+ writer.write_graph graph, false, draw_blocks
473
464
  end
474
465
  else
475
466
  begin
476
467
  IO.popen(['dot', "-T#{out_format}", '-o', out_file], 'w') do |stream|
477
468
  writer = GraphvizWriter.new(stream)
478
469
  hidpi = out_format == :png
479
- writer.write_graph graph, hidpi
470
+ writer.write_graph graph, hidpi, draw_blocks
480
471
  end
481
472
  rescue Errno::ENOENT
482
473
  raise 'Could not run Graphviz - is it installed?'
@@ -565,6 +556,31 @@ module Seafoam
565
556
  raise ArgumentError, 'graph not found' unless graph_found
566
557
  end
567
558
 
559
+ # Prints help.
560
+ def help(*_args)
561
+ @out.puts 'seafoam file.bgv info'
562
+ @out.puts ' file.bgv list'
563
+ @out.puts ' file.bgv[:graph][:node[-edge]] search term...'
564
+ @out.puts ' file.bgv[:graph][:node[-edge]] edges'
565
+ @out.puts ' file.bgv[:graph][:node[-edge]] props'
566
+ @out.puts ' file.bgv:graph:node source'
567
+ @out.puts ' file.bgv:graph render'
568
+ @out.puts ' --spotlight n,n,n...'
569
+ @out.puts ' --out graph.pdf'
570
+ @out.puts ' graph.svg'
571
+ @out.puts ' graph.png'
572
+ @out.puts ' graph.dot'
573
+ @out.puts ' --full-truffle-args'
574
+ @out.puts ' --show-frame-state'
575
+ @out.puts ' --show-pi'
576
+ @out.puts ' --hide-floating'
577
+ @out.puts ' --no-reduce-edges'
578
+ @out.puts ' --draw-blocks'
579
+ @out.puts ' --option key value'
580
+ @out.puts ' --help'
581
+ @out.puts ' --version'
582
+ end
583
+
568
584
  # Prints the version.
569
585
  def version(*args)
570
586
  raise ArgumentError, "unexpected arguments #{args.join(' ')}" unless args.empty?
@@ -598,10 +614,15 @@ module Seafoam
598
614
 
599
615
  # Open a file for the user if possible.
600
616
  def autoopen(file)
601
- if RUBY_PLATFORM.include?('darwin') && @out.tty?
617
+ return unless @out.tty?
618
+
619
+ case RUBY_PLATFORM
620
+ when /darwin/
602
621
  system 'open', file
603
- # Don't worry if it fails.
622
+ when /linux/
623
+ system 'xdg-open', file
604
624
  end
625
+ # Don't worry if it fails.
605
626
  end
606
627
  end
607
628
  end
@@ -0,0 +1,18 @@
1
+ module Seafoam
2
+ module Graal
3
+ # Routines for understanding pi nodes in Graal.
4
+ module Pi
5
+ # Find the actual value behind potentially a chain of pi nodes.
6
+ def self.follow_pi_object(node)
7
+ node = node.edges.find { |edge| edge.props[:name] == 'object' }.from while PI_NODES.include?(node.props.dig(:node_class, :node_class))
8
+ node
9
+ end
10
+
11
+ # Pi nodes add type information.
12
+ PI_NODES = [
13
+ 'org.graalvm.compiler.nodes.PiNode',
14
+ 'org.graalvm.compiler.nodes.PiArrayNode'
15
+ ]
16
+ end
17
+ end
18
+ end
data/lib/seafoam/graph.rb CHANGED
@@ -2,12 +2,14 @@ module Seafoam
2
2
  # A graph, with properties, nodes, and edges. We don't encapsulate the graph
3
3
  # too much - be careful.
4
4
  class Graph
5
- attr_reader :props, :nodes, :edges
5
+ attr_reader :props, :nodes, :edges, :blocks, :new_id
6
6
 
7
7
  def initialize(props = nil)
8
8
  @props = props || {}
9
9
  @nodes = {}
10
10
  @edges = []
11
+ @blocks = []
12
+ @new_id = 0
11
13
  end
12
14
 
13
15
  # Create a node.
@@ -15,6 +17,7 @@ module Seafoam
15
17
  props ||= {}
16
18
  node = Node.new(id, props)
17
19
  @nodes[id] = node
20
+ @new_id = id + 1
18
21
  node
19
22
  end
20
23
 
@@ -27,6 +30,20 @@ module Seafoam
27
30
  to.inputs.push edge
28
31
  edge
29
32
  end
33
+
34
+ # Add a new basic block with given id and node id list.
35
+ def create_block(id, node_ids)
36
+ nodes = node_ids.select { |n| @nodes.key? n }.map { |n| @nodes[n] }
37
+ block = Block.new(id, nodes)
38
+ @blocks.push block
39
+ block
40
+ end
41
+
42
+ def remove_edge(edge)
43
+ edge.from.outputs.delete edge
44
+ edge.to.inputs.delete edge
45
+ edges.delete edge
46
+ end
30
47
  end
31
48
 
32
49
  # A node, with properties, input edges, and output edges.
@@ -88,4 +105,19 @@ module Seafoam
88
105
  "<Edge #{from.id} -> #{to.id}>"
89
106
  end
90
107
  end
108
+
109
+ # A control-flow basic block
110
+ class Block
111
+ attr_reader :id, :nodes
112
+
113
+ def initialize(id, nodes)
114
+ @id = id
115
+ @nodes = nodes
116
+ end
117
+
118
+ # Inspect.
119
+ def inspect
120
+ "<Block #{id}>"
121
+ end
122
+ end
91
123
  end
@@ -7,15 +7,16 @@ module Seafoam
7
7
  end
8
8
 
9
9
  # Write a graph.
10
- def write_graph(graph, hidpi = false)
10
+ def write_graph(graph, hidpi = false, draw_blocks = false)
11
11
  inline_attrs = {}
12
12
  attrs = {}
13
13
  attrs[:dpi] = 200 if hidpi
14
- attrs[:bgcolor] = 'transparent'
14
+ attrs[:bgcolor] = 'white'
15
15
  @stream.puts 'digraph G {'
16
16
  @stream.puts " graph #{write_attrs(attrs)};"
17
17
  write_nodes inline_attrs, graph
18
18
  write_edges inline_attrs, graph
19
+ write_blocks graph if draw_blocks
19
20
  @stream.puts '}'
20
21
  end
21
22
 
@@ -99,6 +100,9 @@ module Seafoam
99
100
  # from a shaded node.
100
101
  next if edge.to.props[:hidden] && edge.from.props[:spotlight] != 'shaded'
101
102
 
103
+ # Skip the edge if it's hidden itself
104
+ next if edge.props[:hidden]
105
+
102
106
  write_edge inline_attrs, edge
103
107
  end
104
108
  end
@@ -166,6 +170,24 @@ module Seafoam
166
170
  end
167
171
  end
168
172
 
173
+ # Write basic block outlines.
174
+ def write_blocks(graph)
175
+ graph.blocks.each do |block|
176
+ @stream.puts " subgraph cluster_block#{block.id} {"
177
+ @stream.puts ' fontname = "Arial";'
178
+ @stream.puts " label = \"B#{block.id}\";"
179
+ @stream.puts ' style=dotted;'
180
+
181
+ block.nodes.each do |node|
182
+ next if node.props[:hidden] || node.props[:inlined]
183
+
184
+ @stream.puts " node#{node.id};"
185
+ end
186
+
187
+ @stream.puts ' }'
188
+ end
189
+ end
190
+
169
191
  # Return attributes for a node or edge modified to 'shade' them in terms
170
192
  # the spotlight functionality - so basically make them light grey.
171
193
  def shade(attrs)
@@ -36,9 +36,7 @@ module Seafoam
36
36
  @out.puts JSON.pretty_generate(prepare_json(object))
37
37
  end
38
38
 
39
- private
40
-
41
- def prepare_json(object)
39
+ def self.prepare_json(object)
42
40
  case object
43
41
  when Float
44
42
  if object.nan?
@@ -1,15 +1,15 @@
1
1
  module Seafoam
2
- module Annotators
3
- # The fallback annotator always applies, and adds some basic annotations.
2
+ module Passes
3
+ # The fallback pass always applies, and adds some basic properties.
4
4
  # Works for example with Truffle AST and call graphs, but also means anyone
5
5
  # can emit a graph with 'label' properties and we can do something useful
6
6
  # with it.
7
- class FallbackAnnotator < Annotator
7
+ class FallbackPass < Pass
8
8
  def self.applies?(_graph)
9
9
  true
10
10
  end
11
11
 
12
- def annotate(graph)
12
+ def apply(graph)
13
13
  graph.nodes.each_value do |node|
14
14
  if node.props[:label].nil? && node.props['label']
15
15
  node.props[:label] = node.props['label']
@@ -1,18 +1,19 @@
1
1
  module Seafoam
2
- module Annotators
3
- # The Graal annotator applies if it looks like it was compiled by Graal or
2
+ module Passes
3
+ # The Graal pass applies if it looks like it was compiled by Graal or
4
4
  # Truffle.
5
- class GraalAnnotator < Annotator
5
+ class GraalPass < Pass
6
6
  def self.applies?(graph)
7
7
  graph.props.values.any? do |v|
8
8
  TRIGGERS.any? { |t| v.to_s.include?(t) }
9
9
  end
10
10
  end
11
11
 
12
- def annotate(graph)
13
- annotate_nodes graph
14
- annotate_edges graph
12
+ def apply(graph)
13
+ apply_nodes graph
14
+ apply_edges graph
15
15
  hide_frame_state graph if @options[:hide_frame_state]
16
+ hide_pi graph if @options[:hide_pi]
16
17
  hide_floating graph if @options[:hide_floating]
17
18
  reduce_edges graph if @options[:reduce_edges]
18
19
  hide_unused_nodes graph
@@ -21,8 +22,10 @@ module Seafoam
21
22
  private
22
23
 
23
24
  # Annotate nodes with their label and kind
24
- def annotate_nodes(graph)
25
+ def apply_nodes(graph)
25
26
  graph.nodes.each_value do |node|
27
+ next if node.props[:label]
28
+
26
29
  # The Java class of the node.
27
30
  node_class = node.props.dig(:node_class, :node_class)
28
31
 
@@ -93,6 +96,11 @@ module Seafoam
93
96
  name_template = 'π'
94
97
  end
95
98
 
99
+ # Use a symbol for PiArrayNode.
100
+ if node_class == 'org.graalvm.compiler.nodes.PiArrayNode'
101
+ name_template = '[π]'
102
+ end
103
+
96
104
  # Use a symbol for PhiNode.
97
105
  if node_class == 'org.graalvm.compiler.nodes.ValuePhiNode'
98
106
  name_template = 'ϕ'
@@ -208,7 +216,7 @@ module Seafoam
208
216
  end
209
217
 
210
218
  # Annotate edges with their label and kind.
211
- def annotate_edges(graph)
219
+ def apply_edges(graph)
212
220
  graph.edges.each do |edge|
213
221
  if edge.to.props.dig(:node_class, :node_class) == 'org.graalvm.compiler.nodes.ValuePhiNode' && edge.props[:name] == 'values'
214
222
  merge_node = edge.to.edges.find { |e| e.props[:name] == 'merge' }.from
@@ -311,6 +319,24 @@ module Seafoam
311
319
  end
312
320
  end
313
321
 
322
+ # Hide pi nodes - they add information for optimisation, but not generally
323
+ # useful day-to-day for understanding the graph. Connect the user to the
324
+ # actual object, which may be several steps away.
325
+ def hide_pi(graph)
326
+ loop do
327
+ umodified = true
328
+ graph.edges.each do |edge|
329
+ next unless Graal::Pi::PI_NODES.include?(edge.from.props.dig(:node_class, :node_class))
330
+
331
+ object = Graal::Pi.follow_pi_object(edge.from)
332
+ graph.create_edge object, edge.to, edge.props.merge({ synthetic: true })
333
+ graph.remove_edge edge
334
+ umodified = false
335
+ end
336
+ break if umodified
337
+ end
338
+ end
339
+
314
340
  # Hide floating nodes. This highlights just the control flow backbone.
315
341
  def hide_floating(graph)
316
342
  graph.nodes.each_value do |node|
@@ -332,15 +358,19 @@ module Seafoam
332
358
  end
333
359
  end
334
360
 
335
- # Hide nodes that have no non-hidden users and no control flow in. These
336
- # would display as a node floating unconnected to the rest of the graph
337
- # otherwise. An exception is made for node with an anchor edge coming in,
338
- # as some guards are anchored like this.
361
+ # Hide nodes that have no non-hidden users or edges and no control flow
362
+ # in. These would display as a node floating unconnected to the rest of
363
+ # the graph otherwise. An exception is made for node with an anchor edge
364
+ # coming in, as some guards are anchored like this.
339
365
  def hide_unused_nodes(graph)
340
366
  loop do
341
367
  modified = false
342
368
  graph.nodes.each_value do |node|
343
- next unless node.outputs.all? { |edge| edge.to.props[:hidden] } &&
369
+ # Call trees are like Graal graphs but don't have these edges -
370
+ # don't hide them.
371
+ next if node.props['truffleCallees']
372
+
373
+ next unless node.outputs.all? { |edge| edge.to.props[:hidden] || edge.props[:hidden] } &&
344
374
  node.inputs.none? { |edge| edge.props[:kind] == 'control' } &&
345
375
  node.inputs.none? { |edge| edge.props[:name] == 'anchor' }
346
376
 
@@ -0,0 +1,58 @@
1
+ module Seafoam
2
+ module Passes
3
+ # The Truffle pass applies if it looks like it was compiled by Truffle.
4
+ class TrufflePass < Pass
5
+ def self.applies?(graph)
6
+ graph.props.values.any? do |v|
7
+ TRIGGERS.any? { |t| v.to_s.include?(t) }
8
+ end
9
+ end
10
+
11
+ def apply(graph)
12
+ simplify_truffle_args graph if @options[:simplify_truffle_args]
13
+ end
14
+
15
+ private
16
+
17
+ # Simplify a Truffle argument load into a single, inlined, node very much
18
+ # like a Graal parameter node.
19
+ def simplify_truffle_args(graph)
20
+ graph.nodes.dup.each_value do |node|
21
+ next unless node.props.dig(:node_class, :node_class) == 'org.graalvm.compiler.nodes.java.LoadIndexedNode'
22
+
23
+ index_node = node.inputs.find { |edge| edge.props[:name] == 'index' }.from
24
+ array_node = Graal::Pi.follow_pi_object(node.inputs.find { |edge| edge.props[:name] == 'array' }.from)
25
+
26
+ next unless index_node.props.dig(:node_class, :node_class) == 'org.graalvm.compiler.nodes.ConstantNode'
27
+ next unless array_node.props.dig(:node_class, :node_class) == 'org.graalvm.compiler.nodes.ParameterNode'
28
+
29
+ node.props[:truffle_arg_load] = true
30
+
31
+ index = index_node.props['rawvalue']
32
+
33
+ arg_node = graph.create_node(graph.new_id, { synthetic: true, inlined: true, label: "T(#{index})", kind: 'input' })
34
+
35
+ node.outputs.each do |output|
36
+ next if output.props[:name] == 'next'
37
+
38
+ graph.create_edge arg_node, output.to, output.props.dup
39
+ graph.remove_edge output
40
+ end
41
+ end
42
+
43
+ graph.nodes.each_value.select { |node| node.props[:truffle_arg_load] }.each do |node|
44
+ control_in = node.inputs.find { |edge| edge.props[:name] == 'next' }
45
+ control_out = node.outputs.find { |edge| edge.props[:name] == 'next' }
46
+ graph.create_edge control_in.from, control_out.to, { name: 'next' }
47
+ graph.remove_edge control_in
48
+ graph.remove_edge control_out
49
+ end
50
+ end
51
+
52
+ # If we see these in the graph properties it's probably a Truffle graph.
53
+ TRIGGERS = %w[
54
+ TruffleCompiler
55
+ ]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,61 @@
1
+ module Seafoam
2
+ # Passes are routines to read the graph and apply properties which tools,
3
+ # such as the render command, can use to show more understandable output.
4
+ module Passes
5
+ # Apply all applicable passes to a graph.
6
+ def self.apply(graph, options = {})
7
+ passes.each do |pass|
8
+ next unless pass.applies?(graph)
9
+
10
+ # Record for information that the pass was applied this graph.
11
+ passes_applied = graph.props[:passes_applied] ||= []
12
+ passes_applied.push pass
13
+
14
+ # Run the pass.
15
+ instance = pass.new(options)
16
+ instance.apply graph
17
+ end
18
+ end
19
+
20
+ # Get a list of all passes in the system.
21
+ def self.passes
22
+ # We have a defined order for passes to run - these passes at the start.
23
+ pre_passes = [
24
+ TrufflePass,
25
+ GraalPass
26
+ ]
27
+
28
+ # The fallback pass runs last.
29
+ post_passes = [
30
+ FallbackPass
31
+ ]
32
+
33
+ # Any extra passes in the middle.
34
+ extra_passes = Pass::SUBCLASSES.dup - pre_passes - post_passes
35
+
36
+ pre_passes + extra_passes + post_passes
37
+ end
38
+ end
39
+
40
+ # The base class for all passes. You must subclass this to be recognized
41
+ # as an pass.
42
+ class Pass
43
+ SUBCLASSES = []
44
+
45
+ def initialize(options = {})
46
+ @options = options
47
+ end
48
+
49
+ def applies?(_graph)
50
+ raise NotImplementedError
51
+ end
52
+
53
+ def apply(_graph)
54
+ raise NotImplementedError
55
+ end
56
+
57
+ def self.inherited(pass)
58
+ SUBCLASSES.push pass
59
+ end
60
+ end
61
+ end
@@ -1,5 +1,5 @@
1
1
  module Seafoam
2
2
  MAJOR_VERSION = 0
3
- MINOR_VERSION = 5
3
+ MINOR_VERSION = 9
4
4
  VERSION = "#{MAJOR_VERSION}.#{MINOR_VERSION}"
5
5
  end