seafoam 0.13 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tsort"
4
+
1
5
  module Seafoam
2
6
  module Passes
3
7
  # The Truffle pass applies if it looks like it was compiled by Truffle.
4
8
  class TrufflePass < Pass
5
- def self.applies?(graph)
6
- graph.props.values.any? do |v|
7
- TRIGGERS.any? { |t| v.to_s.include?(t) }
9
+ class << self
10
+ def applies?(graph)
11
+ graph.props.values.any? do |v|
12
+ TRIGGERS.any? { |t| v.to_s.include?(t) }
13
+ end
8
14
  end
9
15
  end
10
16
 
11
17
  def apply(graph)
12
- simplify_truffle_args graph if @options[:simplify_truffle_args]
18
+ simplify_truffle_args(graph) if @options[:simplify_truffle_args]
19
+ simplify_alloc(graph) if @options[:simplify_alloc]
20
+ hide_reachability_fences(graph) if @options[:hide_reachability_fences]
13
21
  end
14
22
 
15
23
  private
@@ -18,41 +26,163 @@ module Seafoam
18
26
  # like a Graal parameter node.
19
27
  def simplify_truffle_args(graph)
20
28
  graph.nodes.dup.each_value do |node|
21
- next unless node.props.dig(:node_class, :node_class) == 'org.graalvm.compiler.nodes.java.LoadIndexedNode'
29
+ next unless node.node_class == "org.graalvm.compiler.nodes.java.LoadIndexedNode"
22
30
 
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)
31
+ index_node = node.inputs.find { |edge| edge.props[:name] == "index" }.from
32
+ array_node = Graal::Pi.follow_pi_object(node.inputs.find { |edge| edge.props[:name] == "array" }.from)
25
33
 
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'
34
+ next unless index_node.node_class == "org.graalvm.compiler.nodes.ConstantNode"
35
+ next unless array_node.node_class == "org.graalvm.compiler.nodes.ParameterNode"
28
36
 
29
37
  node.props[:truffle_arg_load] = true
30
38
 
31
- index = index_node.props['rawvalue']
39
+ lang_formatter = Seafoam::Passes::TruffleTranslators.get_translator(node)
40
+ index = lang_formatter.translate_argument_load(index_node.props["rawvalue"].to_i)
32
41
 
33
- arg_node = graph.create_node(graph.new_id, { synthetic: true, inlined: true, label: "T(#{index})", kind: 'input' })
42
+ arg_node = graph.create_node(
43
+ graph.new_id,
44
+ { synthetic: true, synthetic_class: "TruffleArgument", inlined: true, label: "T(#{index})", kind: "input" },
45
+ )
46
+
47
+ edges_to_remove = []
34
48
 
35
49
  node.outputs.each do |output|
36
- next if output.props[:name] == 'next'
50
+ next if output.props[:name] == "next"
37
51
 
38
- graph.create_edge arg_node, output.to, output.props.dup
39
- graph.remove_edge output
52
+ graph.create_edge(arg_node, output.to, output.props.dup)
53
+ edges_to_remove << output
40
54
  end
55
+
56
+ edges_to_remove.each { |edge| graph.remove_edge(edge) }
41
57
  end
42
58
 
43
59
  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
60
+ control_in = node.inputs.find { |edge| edge.props[:name] == "next" }
61
+ control_out = node.outputs.find { |edge| edge.props[:name] == "next" }
62
+ graph.create_edge(control_in.from, control_out.to, { name: "next" })
63
+ graph.remove_edge(control_in)
64
+ graph.remove_edge(control_out)
65
+ end
66
+ end
67
+
68
+ # Hide nodes that are uninteresting inputs to an allocation node. These
69
+ # are constants that are null or 0.
70
+ def simplify_alloc(graph)
71
+ commit_allocation_nodes = graph.nodes.each_value.select do |node|
72
+ node.node_class == "org.graalvm.compiler.nodes.virtual.CommitAllocationNode"
73
+ end
74
+
75
+ commit_allocation_nodes.each do |commit_allocation_node|
76
+ control_flow_pred = commit_allocation_node.inputs.first
77
+ control_flow_next = commit_allocation_node.outputs.first
78
+
79
+ objects = []
80
+ virtual_to_object = {}
81
+
82
+ # First step to fill virtual_to_object and avoid ordering issues
83
+ commit_allocation_node.props.each_pair do |key, value|
84
+ m = /^object\((\d+)\)$/.match(key)
85
+ next unless m
86
+
87
+ virtual_id = m[1].to_i
88
+
89
+ m = /^([[:alnum:]$]+(?:\[\])?)\[([0-9,]+)\]$/.match(value)
90
+
91
+ unless m
92
+ raise "Unexpected value in allocation node: '#{value}'"
93
+ end
94
+
95
+ class_name, values = m.captures
96
+ values = values.split(",").map(&:to_i)
97
+ virtual_node = graph.nodes[virtual_id]
98
+ if virtual_node.node_class == "org.graalvm.compiler.nodes.virtual.VirtualArrayNode"
99
+ label = "New #{class_name[0...-1]}#{virtual_node.props["length"]}]"
100
+ fields = values.size.times.to_a
101
+ else
102
+ label = "New #{class_name}"
103
+ fields = virtual_node.props["fields"].map { |field| field[:name] }
104
+ end
105
+ raise unless fields.size == values.size
106
+
107
+ new_node = graph.create_node(
108
+ graph.new_id,
109
+ { synthetic: true, synthetic_class: "TruffleNew", label: label, kind: "alloc" },
110
+ )
111
+
112
+ object = [new_node, virtual_node, fields, values]
113
+ objects << object
114
+ virtual_to_object[virtual_id] = object
115
+ end
116
+
117
+ # Topological sort of the new nodes in the control flow according to data dependencies
118
+ # There can be cycles (e.g., instances referring one another),
119
+ # so we use TSort.strongly_connected_components instead of TSort.tsort.
120
+ objects = TSort.strongly_connected_components(
121
+ objects.method(:each),
122
+ lambda do |(_new_node, _virtual_node, _fields, values), &b|
123
+ values.each do |value_id|
124
+ usage = virtual_to_object[value_id]
125
+ b.call(usage) if usage
126
+ end
127
+ end,
128
+ ).reduce(:concat)
129
+
130
+ prev = control_flow_pred.from
131
+ objects.each do |new_node, virtual_node, fields, values|
132
+ graph.create_edge(prev, new_node, control_flow_pred.props)
133
+
134
+ allocated_object_node = virtual_node.outputs.find do |output|
135
+ output.to.node_class == "org.graalvm.compiler.nodes.virtual.AllocatedObjectNode"
136
+ end
137
+ if allocated_object_node
138
+ allocated_object_node = allocated_object_node.to
139
+
140
+ allocated_object_node.outputs.each do |edge|
141
+ graph.create_edge(new_node, edge.to, edge.props)
142
+ end
143
+
144
+ allocated_object_node.props[:hidden] = true
145
+ end
146
+
147
+ fields.zip(values) do |field, value_id|
148
+ value_node = virtual_to_object[value_id]&.first || graph.nodes[value_id]
149
+ if @options[:hide_null_fields] &&
150
+ (value_node.node_class == "org.graalvm.compiler.nodes.ConstantNode") &&
151
+ ["Object[null]", "0"].include?(value_node.props["rawvalue"])
152
+ value_node.props[:hidden] = true
153
+ else
154
+ graph.create_edge(value_node, new_node, { name: field })
155
+ end
156
+ end
157
+
158
+ virtual_node.props[:hidden] = true
159
+
160
+ prev = new_node
161
+ end
162
+ graph.create_edge(prev, control_flow_next.to, control_flow_next.props)
163
+
164
+ commit_allocation_node.props[:hidden] = true
165
+ end
166
+ end
167
+
168
+ # Hide reachability fences - they're just really boring.
169
+ def hide_reachability_fences(graph)
170
+ graph.nodes.each_value do |node|
171
+ next unless node.node_class == "org.graalvm.compiler.nodes.java.ReachabilityFenceNode"
172
+
173
+ pred = node.inputs.find { |edge| edge.props[:name] == "next" }
174
+ succ = node.outputs.find { |edge| edge.props[:name] == "next" }
175
+
176
+ graph.create_edge(pred.from, succ.to, pred.props.merge({ synthetic: true }))
177
+
178
+ node.props[:hidden] = true
179
+ pred.props[:hidden] = true
180
+ succ.props[:hidden] = true
49
181
  end
50
182
  end
51
183
 
52
184
  # If we see these in the graph properties it's probably a Truffle graph.
53
- TRIGGERS = %w[
54
- TruffleCompiler
55
- ]
185
+ TRIGGERS = ["TruffleCompiler", "TruffleFinal"]
56
186
  end
57
187
  end
58
188
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seafoam
4
+ module Passes
5
+ module TruffleTranslators
6
+ class Default
7
+ def translate_argument_load(index)
8
+ index.to_s
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Seafoam
6
+ module Passes
7
+ module TruffleTranslators
8
+ autoload :Default, "seafoam/passes/truffle_translators/default"
9
+ autoload :TruffleRuby, "seafoam/passes/truffle_translators/truffleruby"
10
+
11
+ TRUFFLE_LANGUAGES = {
12
+ "org.truffleruby" => "TruffleRuby",
13
+ }
14
+
15
+ class << self
16
+ def get_translator(node)
17
+ translator = node.visit_outputs(:bfs) do |entry|
18
+ declaring_class = entry.props["code"] || entry.props.dig("nodeSourcePosition", :method)
19
+
20
+ if declaring_class
21
+ subpackage = declaring_class[:declaring_class].match(/^(\w+\.\w+)\./)[1]
22
+ translator = TRUFFLE_LANGUAGES[subpackage]
23
+
24
+ break const_get(translator).new if translator
25
+ end
26
+ end
27
+
28
+ translator || Default.new
29
+ end
30
+ end
31
+
32
+ class Default
33
+ def translate_argument_load(index)
34
+ index.to_s
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Seafoam
4
+ module Passes
5
+ module TruffleTranslators
6
+ class TruffleRuby < Default
7
+ TRUFFLERUBY_ARGS = [
8
+ "DECLARATION_FRAME",
9
+ "CALLER_SPECIAL_VARIABLES",
10
+ "METHOD",
11
+ "DECLARATION_CONTEXT",
12
+ "FRAME_ON_STACK_MARKER",
13
+ "SELF",
14
+ "BLOCK",
15
+ "DESCRIPTOR",
16
+ ]
17
+
18
+ def translate_argument_load(index)
19
+ index >= TRUFFLERUBY_ARGS.size ? "args[#{index - TRUFFLERUBY_ARGS.size}]" : TRUFFLERUBY_ARGS[index]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,39 +1,43 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seafoam
2
4
  # Passes are routines to read the graph and apply properties which tools,
3
5
  # such as the render command, can use to show more understandable output.
4
6
  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
7
+ class << self
8
+ # Apply all applicable passes to a graph.
9
+ def apply(graph, options = {})
10
+ passes.each do |pass|
11
+ next unless pass.applies?(graph)
12
+
13
+ # Record for information that the pass was applied this graph.
14
+ passes_applied = graph.props[:passes_applied] ||= []
15
+ passes_applied.push(pass)
16
+
17
+ # Run the pass.
18
+ instance = pass.new(options)
19
+ instance.apply(graph)
20
+ end
17
21
  end
18
- end
19
22
 
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
- ]
23
+ # Get a list of all passes in the system.
24
+ def passes
25
+ # We have a defined order for passes to run - these passes at the start.
26
+ pre_passes = [
27
+ TrufflePass,
28
+ GraalPass,
29
+ ]
27
30
 
28
- # The fallback pass runs last.
29
- post_passes = [
30
- FallbackPass
31
- ]
31
+ # The fallback pass runs last.
32
+ post_passes = [
33
+ FallbackPass,
34
+ ]
32
35
 
33
- # Any extra passes in the middle.
34
- extra_passes = Pass::SUBCLASSES.dup - pre_passes - post_passes
36
+ # Any extra passes in the middle.
37
+ extra_passes = Pass::SUBCLASSES.dup - pre_passes - post_passes
35
38
 
36
- pre_passes + extra_passes + post_passes
39
+ pre_passes + extra_passes + post_passes
40
+ end
37
41
  end
38
42
  end
39
43
 
@@ -54,8 +58,11 @@ module Seafoam
54
58
  raise NotImplementedError
55
59
  end
56
60
 
57
- def self.inherited(pass)
58
- SUBCLASSES.push pass
61
+ class << self
62
+ def inherited(pass)
63
+ super
64
+ SUBCLASSES.push(pass)
65
+ end
59
66
  end
60
67
  end
61
68
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Seafoam
6
+ class BFS
7
+ attr_reader :root
8
+
9
+ def initialize(root)
10
+ @root = root
11
+ end
12
+
13
+ def search(&block)
14
+ queue = root.outputs.collect(&:to)
15
+ visited = Set.new
16
+
17
+ until queue.empty?
18
+ entry = queue.shift
19
+ visited << entry
20
+
21
+ result = entry.visit(&block)
22
+
23
+ if result
24
+ return result
25
+ else
26
+ entry.outputs.collect(&:to).each do |child|
27
+ queue << child unless visited.include?(child)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seafoam
2
4
  # Spotlight can *light* nodes, which makes them visible, their adjacent nodes
3
5
  # visible by grey, and other nodes invisible. Multiple nodes can be *lit*.
@@ -9,11 +11,11 @@ module Seafoam
9
11
  # Mark a node as lit by the spotlight.
10
12
  def light(node)
11
13
  # This node is lit.
12
- node.props[:spotlight] = 'lit'
14
+ node.props[:spotlight] = "lit"
13
15
 
14
16
  # Adjacent nodes are shaded, if they haven't be lit themselvs.
15
17
  node.adjacent.each do |adjacent|
16
- adjacent.props[:spotlight] ||= 'shaded'
18
+ adjacent.props[:spotlight] ||= "shaded"
17
19
  end
18
20
  end
19
21
 
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Seafoam
2
4
  MAJOR_VERSION = 0
3
- MINOR_VERSION = 13
5
+ MINOR_VERSION = 15
4
6
  VERSION = "#{MAJOR_VERSION}.#{MINOR_VERSION}"
5
7
  end
data/lib/seafoam.rb CHANGED
@@ -1,20 +1,24 @@
1
- require 'seafoam/version'
2
- require 'seafoam/binary/io_binary_reader'
3
- require 'seafoam/bgv/bgv_parser'
4
- require 'seafoam/colors'
5
- require 'seafoam/graph'
6
- require 'seafoam/graal/graph_description'
7
- require 'seafoam/graal/source'
8
- require 'seafoam/graal/pi'
9
- require 'seafoam/passes'
10
- require 'seafoam/passes/truffle'
11
- require 'seafoam/passes/graal'
12
- require 'seafoam/passes/fallback'
13
- require 'seafoam/spotlight'
14
- require 'seafoam/isabelle_writer'
15
- require 'seafoam/json_writer'
16
- require 'seafoam/graphviz_writer'
17
- require 'seafoam/mermaid_writer'
18
- require 'seafoam/markdown_writer'
19
- require 'seafoam/commands'
20
- require 'seafoam/formatters/formatters'
1
+ # frozen_string_literal: true
2
+
3
+ require "seafoam/version"
4
+ require "seafoam/binary/io_binary_reader"
5
+ require "seafoam/bgv/bgv_parser"
6
+ require "seafoam/colors"
7
+ require "seafoam/graph"
8
+ require "seafoam/graal/graph_description"
9
+ require "seafoam/graal/source"
10
+ require "seafoam/graal/pi"
11
+ require "seafoam/passes"
12
+ require "seafoam/passes/truffle_translators/translators"
13
+ require "seafoam/passes/truffle"
14
+ require "seafoam/passes/graal"
15
+ require "seafoam/passes/fallback"
16
+ require "seafoam/spotlight"
17
+ require "seafoam/isabelle_writer"
18
+ require "seafoam/json_writer"
19
+ require "seafoam/graphviz_writer"
20
+ require "seafoam/mermaid_writer"
21
+ require "seafoam/markdown_writer"
22
+ require "seafoam/commands"
23
+ require "seafoam/formatters/formatters"
24
+ require "seafoam/search"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seafoam
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.13'
4
+ version: '0.15'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Seaton
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-16 00:00:00.000000000 Z
11
+ date: 2023-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -39,21 +39,21 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.8'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rubocop
42
+ name: rubocop-shopify
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0.74'
47
+ version: 2.9.0
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0.74'
55
- description:
56
- email:
54
+ version: 2.9.0
55
+ description:
56
+ email:
57
57
  executables:
58
58
  - seafoam
59
59
  - bgv2json
@@ -86,13 +86,20 @@ files:
86
86
  - lib/seafoam/passes/fallback.rb
87
87
  - lib/seafoam/passes/graal.rb
88
88
  - lib/seafoam/passes/truffle.rb
89
+ - lib/seafoam/passes/truffle_translators/default.rb
90
+ - lib/seafoam/passes/truffle_translators/translators.rb
91
+ - lib/seafoam/passes/truffle_translators/truffleruby.rb
92
+ - lib/seafoam/search.rb
89
93
  - lib/seafoam/spotlight.rb
90
94
  - lib/seafoam/version.rb
91
95
  homepage: https://github.com/Shopify/seafoam
92
96
  licenses:
93
97
  - MIT
94
- metadata: {}
95
- post_install_message:
98
+ metadata:
99
+ bug_tracker_uri: https://github.com/Shopify/seafoam/issues
100
+ source_code_uri: https://github.com/Shopify/seafoam
101
+ allowed_push_host: https://rubygems.org
102
+ post_install_message:
96
103
  rdoc_options: []
97
104
  require_paths:
98
105
  - lib
@@ -100,7 +107,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
107
  requirements:
101
108
  - - ">="
102
109
  - !ruby/object:Gem::Version
103
- version: 2.5.9
110
+ version: 2.7.0
104
111
  required_rubygems_version: !ruby/object:Gem::Requirement
105
112
  requirements:
106
113
  - - ">="
@@ -108,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
115
  version: '0'
109
116
  requirements: []
110
117
  rubygems_version: 3.3.3
111
- signing_key:
118
+ signing_key:
112
119
  specification_version: 4
113
120
  summary: A tool for working with compiler graphs
114
121
  test_files: []