seafoam 0.11 → 0.14

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.
@@ -1,15 +1,22 @@
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]
13
20
  end
14
21
 
15
22
  private
@@ -18,41 +25,132 @@ module Seafoam
18
25
  # like a Graal parameter node.
19
26
  def simplify_truffle_args(graph)
20
27
  graph.nodes.dup.each_value do |node|
21
- next unless node.props.dig(:node_class, :node_class) == 'org.graalvm.compiler.nodes.java.LoadIndexedNode'
28
+ next unless node.node_class == "org.graalvm.compiler.nodes.java.LoadIndexedNode"
22
29
 
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)
30
+ index_node = node.inputs.find { |edge| edge.props[:name] == "index" }.from
31
+ array_node = Graal::Pi.follow_pi_object(node.inputs.find { |edge| edge.props[:name] == "array" }.from)
25
32
 
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'
33
+ next unless index_node.node_class == "org.graalvm.compiler.nodes.ConstantNode"
34
+ next unless array_node.node_class == "org.graalvm.compiler.nodes.ParameterNode"
28
35
 
29
36
  node.props[:truffle_arg_load] = true
30
37
 
31
- index = index_node.props['rawvalue']
38
+ index = index_node.props["rawvalue"]
32
39
 
33
- arg_node = graph.create_node(graph.new_id, { synthetic: true, inlined: true, label: "T(#{index})", kind: 'input' })
40
+ arg_node = graph.create_node(graph.new_id,
41
+ { synthetic: true, inlined: true, label: "T(#{index})", kind: "input" })
34
42
 
35
43
  node.outputs.each do |output|
36
- next if output.props[:name] == 'next'
44
+ next if output.props[:name] == "next"
37
45
 
38
- graph.create_edge arg_node, output.to, output.props.dup
39
- graph.remove_edge output
46
+ graph.create_edge(arg_node, output.to, output.props.dup)
47
+ graph.remove_edge(output)
40
48
  end
41
49
  end
42
50
 
43
51
  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
52
+ control_in = node.inputs.find { |edge| edge.props[:name] == "next" }
53
+ control_out = node.outputs.find { |edge| edge.props[:name] == "next" }
54
+ graph.create_edge(control_in.from, control_out.to, { name: "next" })
55
+ graph.remove_edge(control_in)
56
+ graph.remove_edge(control_out)
57
+ end
58
+ end
59
+
60
+ # Hide nodes that are uninteresting inputs to an allocation node. These
61
+ # are constants that are null or 0.
62
+ def simplify_alloc(graph)
63
+ commit_allocation_nodes = graph.nodes.each_value.select do |node|
64
+ node.node_class == "org.graalvm.compiler.nodes.virtual.CommitAllocationNode"
65
+ end
66
+
67
+ commit_allocation_nodes.each do |commit_allocation_node|
68
+ control_flow_pred = commit_allocation_node.inputs.first
69
+ control_flow_next = commit_allocation_node.outputs.first
70
+
71
+ objects = []
72
+ virtual_to_object = {}
73
+
74
+ # First step to fill virtual_to_object and avoid ordering issues
75
+ commit_allocation_node.props.each_pair do |key, value|
76
+ m = /^object\((\d+)\)$/.match(key)
77
+ next unless m
78
+
79
+ virtual_id = m[1].to_i
80
+
81
+ (m = /^(\w+(?:\[\])?)\[([0-9,]+)\]$/.match(value)) || raise(value)
82
+ class_name, values = m.captures
83
+ values = values.split(",").map(&:to_i)
84
+ virtual_node = graph.nodes[virtual_id]
85
+ if virtual_node.node_class == "org.graalvm.compiler.nodes.virtual.VirtualArrayNode"
86
+ label = "New #{class_name[0...-1]}#{virtual_node.props["length"]}]"
87
+ fields = values.size.times.to_a
88
+ else
89
+ label = "New #{class_name}"
90
+ fields = virtual_node.props["fields"].map { |field| field[:name] }
91
+ end
92
+ raise unless fields.size == values.size
93
+
94
+ new_node = graph.create_node(graph.new_id, { synthetic: true, label: label, kind: "alloc" })
95
+
96
+ object = [new_node, virtual_node, fields, values]
97
+ objects << object
98
+ virtual_to_object[virtual_id] = object
99
+ end
100
+
101
+ # Topological sort of the new nodes in the control flow according to data dependencies
102
+ # There can be cycles (e.g., instances referring one another),
103
+ # so we use TSort.strongly_connected_components instead of TSort.tsort.
104
+ objects = TSort.strongly_connected_components(
105
+ objects.method(:each),
106
+ lambda do |(_new_node, _virtual_node, _fields, values), &b|
107
+ values.each do |value_id|
108
+ usage = virtual_to_object[value_id]
109
+ b.call(usage) if usage
110
+ end
111
+ end
112
+ ).reduce(:concat)
113
+
114
+ prev = control_flow_pred.from
115
+ objects.each do |new_node, virtual_node, fields, values|
116
+ graph.create_edge(prev, new_node, control_flow_pred.props)
117
+
118
+ allocated_object_node = virtual_node.outputs.find do |output|
119
+ output.to.node_class == "org.graalvm.compiler.nodes.virtual.AllocatedObjectNode"
120
+ end
121
+ if allocated_object_node
122
+ allocated_object_node = allocated_object_node.to
123
+
124
+ allocated_object_node.outputs.each do |edge|
125
+ graph.create_edge(new_node, edge.to, edge.props)
126
+ end
127
+
128
+ allocated_object_node.props[:hidden] = true
129
+ end
130
+
131
+ fields.zip(values) do |field, value_id|
132
+ value_node = virtual_to_object[value_id]&.first || graph.nodes[value_id]
133
+ if @options[:hide_null_fields] &&
134
+ (value_node.node_class == "org.graalvm.compiler.nodes.ConstantNode") &&
135
+ ["Object[null]", "0"].include?(value_node.props["rawvalue"])
136
+ value_node.props[:hidden] = true
137
+ else
138
+ graph.create_edge(value_node, new_node, { name: field })
139
+ end
140
+ end
141
+
142
+ virtual_node.props[:hidden] = true
143
+
144
+ prev = new_node
145
+ end
146
+ graph.create_edge(prev, control_flow_next.to, control_flow_next.props)
147
+
148
+ commit_allocation_node.props[:hidden] = true
49
149
  end
50
150
  end
51
151
 
52
152
  # If we see these in the graph properties it's probably a Truffle graph.
53
- TRIGGERS = %w[
54
- TruffleCompiler
55
- ]
153
+ TRIGGERS = ["TruffleCompiler"]
56
154
  end
57
155
  end
58
156
  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
@@ -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 = 11
5
+ MINOR_VERSION = 14
4
6
  VERSION = "#{MAJOR_VERSION}.#{MINOR_VERSION}"
5
7
  end
data/lib/seafoam.rb CHANGED
@@ -1,20 +1,22 @@
1
- require 'seafoam/version'
2
- require 'seafoam/binary/io_binary_reader'
3
- require 'seafoam/bgv/bgv_parser'
4
- require 'seafoam/cfg/cfg_parser'
5
- require 'seafoam/cfg/disassembler'
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'
13
- require 'seafoam/passes/graal'
14
- require 'seafoam/passes/fallback'
15
- require 'seafoam/spotlight'
16
- require 'seafoam/isabelle_writer'
17
- require 'seafoam/json_writer'
18
- require 'seafoam/graphviz_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"
13
+ require "seafoam/passes/graal"
14
+ require "seafoam/passes/fallback"
15
+ require "seafoam/spotlight"
16
+ require "seafoam/isabelle_writer"
17
+ require "seafoam/json_writer"
18
+ require "seafoam/graphviz_writer"
19
+ require "seafoam/mermaid_writer"
20
+ require "seafoam/markdown_writer"
21
+ require "seafoam/commands"
22
+ 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.11'
4
+ version: '0.14'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Seaton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-01-26 00:00:00.000000000 Z
11
+ date: 2022-09-02 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
@@ -67,38 +39,34 @@ dependencies:
67
39
  - !ruby/object:Gem::Version
68
40
  version: '3.8'
69
41
  - !ruby/object:Gem::Dependency
70
- name: rubocop
42
+ name: rubocop-shopify
71
43
  requirement: !ruby/object:Gem::Requirement
72
44
  requirements:
73
45
  - - "~>"
74
46
  - !ruby/object:Gem::Version
75
- version: '0.74'
47
+ version: 2.9.0
76
48
  type: :development
77
49
  prerelease: false
78
50
  version_requirements: !ruby/object:Gem::Requirement
79
51
  requirements:
80
52
  - - "~>"
81
53
  - !ruby/object:Gem::Version
82
- version: '0.74'
54
+ version: 2.9.0
83
55
  description:
84
56
  email:
85
57
  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
104
72
  - lib/seafoam/formatters/base.rb
@@ -112,6 +80,8 @@ files:
112
80
  - lib/seafoam/graphviz_writer.rb
113
81
  - lib/seafoam/isabelle_writer.rb
114
82
  - lib/seafoam/json_writer.rb
83
+ - lib/seafoam/markdown_writer.rb
84
+ - lib/seafoam/mermaid_writer.rb
115
85
  - lib/seafoam/passes.rb
116
86
  - lib/seafoam/passes/fallback.rb
117
87
  - lib/seafoam/passes/graal.rb
@@ -130,15 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
130
100
  requirements:
131
101
  - - ">="
132
102
  - !ruby/object:Gem::Version
133
- version: 2.5.9
103
+ version: 2.7.0
134
104
  required_rubygems_version: !ruby/object:Gem::Requirement
135
105
  requirements:
136
106
  - - ">="
137
107
  - !ruby/object:Gem::Version
138
108
  version: '0'
139
109
  requirements: []
140
- rubyforge_project:
141
- rubygems_version: 2.7.6.3
110
+ rubygems_version: 3.3.7
142
111
  signing_key:
143
112
  specification_version: 4
144
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