seafoam 0.11 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -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