seafoam 0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/probots.yml +2 -0
- data/.github/workflows/rubocop.yml +10 -0
- data/.github/workflows/specs.yml +19 -0
- data/.gitignore +7 -0
- data/.rubocop.yml +34 -0
- data/.ruby-version +1 -0
- data/.seafoam/config +1 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +5 -0
- data/Gemfile +2 -0
- data/LICENSE.md +7 -0
- data/README.md +298 -0
- data/bin/bgv2isabelle +53 -0
- data/bin/bgv2json +42 -0
- data/bin/seafoam +24 -0
- data/docs/annotators.md +43 -0
- data/docs/bgv.md +284 -0
- data/docs/getting-graphs.md +47 -0
- data/examples/Fib.java +24 -0
- data/examples/MatMult.java +39 -0
- data/examples/fib-java.bgv +0 -0
- data/examples/fib-js.bgv +0 -0
- data/examples/fib-ruby.bgv +0 -0
- data/examples/fib.js +15 -0
- data/examples/fib.rb +15 -0
- data/examples/identity.bgv +0 -0
- data/examples/identity.rb +13 -0
- data/examples/java/Irreducible.j +35 -0
- data/examples/java/IrreducibleDecompiled.java +21 -0
- data/examples/java/JavaExamples.java +418 -0
- data/examples/java/exampleArithOperator.bgv +0 -0
- data/examples/java/exampleArithOperator.cfg +925 -0
- data/examples/java/exampleArrayAllocation.bgv +0 -0
- data/examples/java/exampleArrayAllocation.cfg +5268 -0
- data/examples/java/exampleArrayRead.bgv +0 -0
- data/examples/java/exampleArrayRead.cfg +2263 -0
- data/examples/java/exampleArrayWrite.bgv +0 -0
- data/examples/java/exampleArrayWrite.cfg +2315 -0
- data/examples/java/exampleCatch.bgv +0 -0
- data/examples/java/exampleCatch.cfg +4150 -0
- data/examples/java/exampleCompareOperator.bgv +0 -0
- data/examples/java/exampleCompareOperator.cfg +1109 -0
- data/examples/java/exampleDoubleSynchronized.bgv +0 -0
- data/examples/java/exampleDoubleSynchronized.cfg +26497 -0
- data/examples/java/exampleExactArith.bgv +0 -0
- data/examples/java/exampleExactArith.cfg +1888 -0
- data/examples/java/exampleFieldRead.bgv +0 -0
- data/examples/java/exampleFieldRead.cfg +1228 -0
- data/examples/java/exampleFieldWrite.bgv +0 -0
- data/examples/java/exampleFieldWrite.cfg +1102 -0
- data/examples/java/exampleFor.bgv +0 -0
- data/examples/java/exampleFor.cfg +3936 -0
- data/examples/java/exampleFullEscape.bgv +0 -0
- data/examples/java/exampleFullEscape.cfg +5893 -0
- data/examples/java/exampleIf.bgv +0 -0
- data/examples/java/exampleIf.cfg +2462 -0
- data/examples/java/exampleIfNeverTaken.bgv +0 -0
- data/examples/java/exampleIfNeverTaken.cfg +2476 -0
- data/examples/java/exampleInstanceOfManyImpls.bgv +0 -0
- data/examples/java/exampleInstanceOfManyImpls.cfg +6391 -0
- data/examples/java/exampleInstanceOfOneImpl.bgv +0 -0
- data/examples/java/exampleInstanceOfOneImpl.cfg +2604 -0
- data/examples/java/exampleIntSwitch.bgv +0 -0
- data/examples/java/exampleIntSwitch.cfg +3121 -0
- data/examples/java/exampleInterfaceCallManyImpls.bgv +0 -0
- data/examples/java/exampleInterfaceCallManyImpls.cfg +1358 -0
- data/examples/java/exampleInterfaceCallOneImpl.bgv +0 -0
- data/examples/java/exampleInterfaceCallOneImpl.cfg +3859 -0
- data/examples/java/exampleLocalInstanceOf.bgv +0 -0
- data/examples/java/exampleLocalInstanceOf.cfg +5276 -0
- data/examples/java/exampleLocalSynchronized.bgv +0 -0
- data/examples/java/exampleLocalSynchronized.cfg +1364 -0
- data/examples/java/exampleLocalVariables.bgv +0 -0
- data/examples/java/exampleLocalVariables.cfg +1195 -0
- data/examples/java/exampleLocalVariablesState.bgv +0 -0
- data/examples/java/exampleLocalVariablesState.cfg +1673 -0
- data/examples/java/exampleNestedWhile.bgv +0 -0
- data/examples/java/exampleNestedWhile.cfg +15499 -0
- data/examples/java/exampleNestedWhileBreak.bgv +0 -0
- data/examples/java/exampleNestedWhileBreak.cfg +11162 -0
- data/examples/java/exampleNoEscape.bgv +0 -0
- data/examples/java/exampleNoEscape.cfg +974 -0
- data/examples/java/exampleObjectAllocation.bgv +0 -0
- data/examples/java/exampleObjectAllocation.cfg +5287 -0
- data/examples/java/examplePartialEscape.bgv +0 -0
- data/examples/java/examplePartialEscape.cfg +7042 -0
- data/examples/java/examplePhi.bgv +0 -0
- data/examples/java/examplePhi.cfg +3227 -0
- data/examples/java/exampleReducible.bgv +0 -0
- data/examples/java/exampleReducible.cfg +5578 -0
- data/examples/java/exampleSimpleCall.bgv +0 -0
- data/examples/java/exampleSimpleCall.cfg +1435 -0
- data/examples/java/exampleStamp.bgv +0 -0
- data/examples/java/exampleStamp.cfg +913 -0
- data/examples/java/exampleStaticCall.bgv +0 -0
- data/examples/java/exampleStaticCall.cfg +1154 -0
- data/examples/java/exampleStringSwitch.bgv +0 -0
- data/examples/java/exampleStringSwitch.cfg +15377 -0
- data/examples/java/exampleSynchronized.bgv +0 -0
- data/examples/java/exampleSynchronized.cfg +26027 -0
- data/examples/java/exampleThrow.bgv +0 -0
- data/examples/java/exampleThrow.cfg +780 -0
- data/examples/java/exampleThrowCatch.bgv +0 -0
- data/examples/java/exampleThrowCatch.cfg +744 -0
- data/examples/java/exampleUnsafeRead.bgv +0 -0
- data/examples/java/exampleUnsafeRead.cfg +912 -0
- data/examples/java/exampleUnsafeWrite.bgv +0 -0
- data/examples/java/exampleUnsafeWrite.cfg +962 -0
- data/examples/java/exampleWhile.bgv +0 -0
- data/examples/java/exampleWhile.cfg +3936 -0
- data/examples/java/exampleWhileBreak.bgv +0 -0
- data/examples/java/exampleWhileBreak.cfg +5963 -0
- data/examples/matmult-java.bgv +0 -0
- data/examples/matmult-ruby.bgv +0 -0
- data/examples/matmult.rb +29 -0
- data/examples/overflow.bgv +0 -0
- data/examples/overflow.rb +13 -0
- data/lib/seafoam.rb +13 -0
- data/lib/seafoam/annotators.rb +54 -0
- data/lib/seafoam/annotators/fallback.rb +27 -0
- data/lib/seafoam/annotators/graal.rb +376 -0
- data/lib/seafoam/bgv/bgv_parser.rb +602 -0
- data/lib/seafoam/binary/binary_reader.rb +21 -0
- data/lib/seafoam/binary/io_binary_reader.rb +88 -0
- data/lib/seafoam/colors.rb +18 -0
- data/lib/seafoam/commands.rb +447 -0
- data/lib/seafoam/config.rb +34 -0
- data/lib/seafoam/graph.rb +91 -0
- data/lib/seafoam/graphviz_writer.rb +213 -0
- data/lib/seafoam/spotlight.rb +28 -0
- data/lib/seafoam/version.rb +5 -0
- data/seafoam.gemspec +20 -0
- data/spec/seafoam/annotators/fallback_spec.rb +69 -0
- data/spec/seafoam/annotators/graal_spec.rb +96 -0
- data/spec/seafoam/annotators_spec.rb +61 -0
- data/spec/seafoam/bgv/bgv_parser_spec.rb +144 -0
- data/spec/seafoam/bgv/fixtures/not.bgv +1 -0
- data/spec/seafoam/bgv/fixtures/unsupported.bgv +1 -0
- data/spec/seafoam/binary/io_binary_reader_spec.rb +176 -0
- data/spec/seafoam/command_spec.rb +252 -0
- data/spec/seafoam/graph_spec.rb +172 -0
- data/spec/seafoam/graphviz_writer_spec.rb +63 -0
- data/spec/seafoam/spec_helpers.rb +30 -0
- data/spec/seafoam/spotlight_spec.rb +38 -0
- data/tools/render-all +36 -0
- metadata +238 -0
Binary file
|
Binary file
|
data/examples/matmult.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# % ruby --experimental-options --engine.CompileOnly=matmult --engine.Inlining=false --engine.OSR=false --vm.Dgraal.Dump=Truffle:2 matmult.rb 100
|
2
|
+
|
3
|
+
def matmult(size, a, b, c)
|
4
|
+
# Normally we'd write a loop with blocks, but then we'd be reliant on
|
5
|
+
# inlining.
|
6
|
+
i = 0
|
7
|
+
while i < size
|
8
|
+
j = 0
|
9
|
+
while j < size
|
10
|
+
k = 0
|
11
|
+
while k < size
|
12
|
+
c[i][j] += a[i][k] * b[k][j]
|
13
|
+
k += 1
|
14
|
+
end
|
15
|
+
j += 1
|
16
|
+
end
|
17
|
+
i += 1
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
loop do
|
22
|
+
ARGV.each do |arg|
|
23
|
+
size = Integer(arg)
|
24
|
+
a = size.times.map { size.times.map { rand } }
|
25
|
+
b = size.times.map { size.times.map { rand } }
|
26
|
+
c = size.times.map { size.times.map { 0.0 } }
|
27
|
+
matmult size, a, b, c
|
28
|
+
end
|
29
|
+
end
|
Binary file
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# % ruby --experimental-options --engine.CompileOnly=add --engine.Inlining=false --engine.OSR=false --vm.Dgraal.Dump=Truffle:2 overflow.rb 1 2 3
|
2
|
+
|
3
|
+
# The purpose of this add function is to show what Ruby arithmetic with overflow looks like.
|
4
|
+
|
5
|
+
def add(a, b)
|
6
|
+
a + b
|
7
|
+
end
|
8
|
+
|
9
|
+
loop do
|
10
|
+
ARGV.each do |arg|
|
11
|
+
add(Integer(arg), Integer(arg))
|
12
|
+
end
|
13
|
+
end
|
data/lib/seafoam.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'seafoam/version'
|
2
|
+
require 'seafoam/binary/io_binary_reader'
|
3
|
+
require 'seafoam/binary/binary_reader'
|
4
|
+
require 'seafoam/bgv/bgv_parser'
|
5
|
+
require 'seafoam/colors'
|
6
|
+
require 'seafoam/graph'
|
7
|
+
require 'seafoam/annotators'
|
8
|
+
require 'seafoam/annotators/graal'
|
9
|
+
require 'seafoam/annotators/fallback'
|
10
|
+
require 'seafoam/spotlight'
|
11
|
+
require 'seafoam/graphviz_writer'
|
12
|
+
require 'seafoam/config'
|
13
|
+
require 'seafoam/commands'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Seafoam
|
2
|
+
# Annotators 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 Annotators
|
5
|
+
# Apply all applicable annotators to a graph.
|
6
|
+
def self.apply(graph, options = {})
|
7
|
+
annotators.each do |annotator|
|
8
|
+
next unless annotator.applies?(graph)
|
9
|
+
|
10
|
+
# Record for information that the annotator annotated this graph.
|
11
|
+
annotated_by = graph.props[:annotated_by] ||= []
|
12
|
+
annotated_by.push annotator
|
13
|
+
|
14
|
+
# Run the annotator.
|
15
|
+
instance = annotator.new(options)
|
16
|
+
instance.annotate graph
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get a list of all annotators in the system.
|
21
|
+
def self.annotators
|
22
|
+
# Get all subclasses of Annotator.
|
23
|
+
annotators = Annotator::SUBCLASSES.dup
|
24
|
+
|
25
|
+
# We want the FallbackAnnotator to run last.
|
26
|
+
annotators.delete FallbackAnnotator
|
27
|
+
annotators.push FallbackAnnotator
|
28
|
+
|
29
|
+
annotators
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# The base class for all annotators. You must subclass this to be recognized
|
34
|
+
# as an annotator.
|
35
|
+
class Annotator
|
36
|
+
SUBCLASSES = []
|
37
|
+
|
38
|
+
def initialize(options = {})
|
39
|
+
@options = options
|
40
|
+
end
|
41
|
+
|
42
|
+
def applies?(_graph)
|
43
|
+
raise NotImplementedError
|
44
|
+
end
|
45
|
+
|
46
|
+
def annotate(_graph)
|
47
|
+
raise NotImplementedError
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.inherited(annotator)
|
51
|
+
SUBCLASSES.push annotator
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Seafoam
|
2
|
+
module Annotators
|
3
|
+
# The fallback annotator always applies, and adds some basic annotations.
|
4
|
+
# Works for example with Truffle AST and call graphs, but also means anyone
|
5
|
+
# can emit a graph with 'label' properties and we can do something useful
|
6
|
+
# with it.
|
7
|
+
class FallbackAnnotator < Annotator
|
8
|
+
def self.applies?(_graph)
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
def annotate(graph)
|
13
|
+
graph.nodes.each_value do |node|
|
14
|
+
if node.props[:label].nil? && node.props['label']
|
15
|
+
node.props[:label] = node.props['label']
|
16
|
+
end
|
17
|
+
|
18
|
+
node.props[:kind] ||= 'other'
|
19
|
+
end
|
20
|
+
|
21
|
+
graph.edges.each do |edge|
|
22
|
+
edge.props[:kind] ||= 'other'
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,376 @@
|
|
1
|
+
module Seafoam
|
2
|
+
module Annotators
|
3
|
+
# The Graal annotator applies if it looks like it was compiled by Graal or
|
4
|
+
# Truffle.
|
5
|
+
class GraalAnnotator < Annotator
|
6
|
+
def self.applies?(graph)
|
7
|
+
graph.props.values.any? do |v|
|
8
|
+
TRIGGERS.any? { |t| v.to_s.include?(t) }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def annotate(graph)
|
13
|
+
annotate_nodes graph
|
14
|
+
annotate_edges graph
|
15
|
+
hide_frame_state graph if @options[:hide_frame_state]
|
16
|
+
hide_floating graph if @options[:hide_floating]
|
17
|
+
reduce_edges graph if @options[:reduce_edges]
|
18
|
+
hide_unused_nodes graph
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# Annotate nodes with their label and kind
|
24
|
+
def annotate_nodes(graph)
|
25
|
+
graph.nodes.each_value do |node|
|
26
|
+
# The Java class of the node.
|
27
|
+
node_class = node.props.dig(:node_class, :node_class)
|
28
|
+
|
29
|
+
# IGV renders labels using a template, with parts that are replaced
|
30
|
+
# with other properties. We will modify these templates in some
|
31
|
+
# cases.
|
32
|
+
name_template = node.props.dig(:node_class, :name_template)
|
33
|
+
|
34
|
+
# For constant nodes, the rawvalue is a truncated version of the
|
35
|
+
# actual value, which is fully qualified. Instead, produce a simple
|
36
|
+
# version of the value and don't truncate it.
|
37
|
+
if node_class == 'org.graalvm.compiler.nodes.ConstantNode'
|
38
|
+
if node.props['value'] =~ /Object\[Instance<(\w+\.)+(\w*)>\]/
|
39
|
+
node.props['rawvalue'] = "instance:#{Regexp.last_match(2)}"
|
40
|
+
end
|
41
|
+
name_template = 'C({p#rawvalue})'
|
42
|
+
end
|
43
|
+
|
44
|
+
# The template for FixedGuardNode could be simpler.
|
45
|
+
if %w[
|
46
|
+
org.graalvm.compiler.nodes.FixedGuardNode
|
47
|
+
org.graalvm.compiler.nodes.GuardNode
|
48
|
+
].include?(node_class)
|
49
|
+
name_template = if node.props['negated']
|
50
|
+
'Guard not, else {p#reason/s}'
|
51
|
+
else
|
52
|
+
'Guard, else {p#reason/s}'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# The template for InvokeNode could be simpler.
|
57
|
+
if node_class == 'org.graalvm.compiler.nodes.InvokeNode'
|
58
|
+
name_template = 'Call {p#targetMethod/s}'
|
59
|
+
end
|
60
|
+
|
61
|
+
# The template for InvokeWithExceptionNode could be simpler.
|
62
|
+
if node_class == 'org.graalvm.compiler.nodes.InvokeWithExceptionNode'
|
63
|
+
name_template = 'Call {p#targetMethod/s} !'
|
64
|
+
end
|
65
|
+
|
66
|
+
# The template for CommitAllocationNode could be simpler.
|
67
|
+
if node_class == 'org.graalvm.compiler.nodes.virtual.CommitAllocationNode'
|
68
|
+
name_template = 'Alloc'
|
69
|
+
end
|
70
|
+
|
71
|
+
# The template for org.graalvm.compiler.nodes.virtual.VirtualArrayNode includes an ID that we don't normally need.
|
72
|
+
if node_class == 'org.graalvm.compiler.nodes.virtual.VirtualArrayNode'
|
73
|
+
name_template = 'VirtualArray {p#componentType/s}[{p#length}]'
|
74
|
+
end
|
75
|
+
|
76
|
+
# The template for LoadField could be simpler.
|
77
|
+
if node_class == 'org.graalvm.compiler.nodes.java.LoadFieldNode'
|
78
|
+
name_template = 'LoadField {x#field}'
|
79
|
+
end
|
80
|
+
|
81
|
+
# The template for StoreField could be simpler.
|
82
|
+
if node_class == 'org.graalvm.compiler.nodes.java.StoreFieldNode'
|
83
|
+
name_template = 'StoreField {x#field}'
|
84
|
+
end
|
85
|
+
|
86
|
+
# We want to see keys for IntegerSwitchNode.
|
87
|
+
if node_class == 'org.graalvm.compiler.nodes.extended.IntegerSwitchNode'
|
88
|
+
name_template = 'IntegerSwitch {p#keys}'
|
89
|
+
end
|
90
|
+
|
91
|
+
# Use a symbol for PiNode.
|
92
|
+
if node_class == 'org.graalvm.compiler.nodes.PiNode'
|
93
|
+
name_template = 'π'
|
94
|
+
end
|
95
|
+
|
96
|
+
# Use a symbol for PhiNode.
|
97
|
+
if node_class == 'org.graalvm.compiler.nodes.ValuePhiNode'
|
98
|
+
name_template = 'ϕ'
|
99
|
+
end
|
100
|
+
|
101
|
+
# Better template for frame states.
|
102
|
+
if node_class == 'org.graalvm.compiler.nodes.FrameState'
|
103
|
+
name_template = 'FrameState {x#state}'
|
104
|
+
end
|
105
|
+
|
106
|
+
# Show the stamp in an InstanceOfNode.
|
107
|
+
if node_class == 'org.graalvm.compiler.nodes.java.InstanceOfNode'
|
108
|
+
name_template = 'InstanceOf {x#simpleStamp}'
|
109
|
+
end
|
110
|
+
|
111
|
+
if name_template.empty?
|
112
|
+
# If there is no template, then the label is the short name of the
|
113
|
+
# Java class, without '...Node' because that's redundant.
|
114
|
+
short_name = node_class.split('.').last
|
115
|
+
short_name = short_name.slice(0, short_name.size - 'Node'.size) if short_name.end_with?('Node')
|
116
|
+
label = short_name
|
117
|
+
else
|
118
|
+
# Render the template.
|
119
|
+
label = render_name_template(name_template, node)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Annotate interesting stamps.
|
123
|
+
if node.props['stamp'] =~ /(\[\d+ \- \d+\])/
|
124
|
+
node.props[:out_annotation] = Regexp.last_match(1)
|
125
|
+
end
|
126
|
+
|
127
|
+
# That's our label.
|
128
|
+
node.props[:label] = label
|
129
|
+
|
130
|
+
# Set the :kind property.
|
131
|
+
if node_class.start_with?('org.graalvm.compiler.nodes.calc.') ||
|
132
|
+
node_class.start_with?('org.graalvm.compiler.replacements.nodes.arithmetic.')
|
133
|
+
kind = 'calc'
|
134
|
+
else
|
135
|
+
kind = NODE_KIND_MAP[node_class] || 'other'
|
136
|
+
end
|
137
|
+
node.props[:kind] = kind
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Map of node classes to their kind.
|
142
|
+
NODE_KIND_MAP = {
|
143
|
+
'org.graalvm.compiler.nodes.BeginNode' => 'control',
|
144
|
+
'org.graalvm.compiler.nodes.ConstantNode' => 'input',
|
145
|
+
'org.graalvm.compiler.nodes.DeoptimizeNode' => 'effect',
|
146
|
+
'org.graalvm.compiler.nodes.EndNode' => 'control',
|
147
|
+
'org.graalvm.compiler.nodes.extended.IntegerSwitchNode' => 'control',
|
148
|
+
'org.graalvm.compiler.nodes.extended.UnsafeMemoryLoadNode' => 'effect',
|
149
|
+
'org.graalvm.compiler.nodes.extended.UnsafeMemoryStoreNode' => 'effect',
|
150
|
+
'org.graalvm.compiler.nodes.FixedGuardNode' => 'guard',
|
151
|
+
'org.graalvm.compiler.nodes.FrameState' => 'info',
|
152
|
+
'org.graalvm.compiler.nodes.GuardNode' => 'guard',
|
153
|
+
'org.graalvm.compiler.nodes.IfNode' => 'control',
|
154
|
+
'org.graalvm.compiler.nodes.InvokeNode' => 'effect',
|
155
|
+
'org.graalvm.compiler.nodes.InvokeWithExceptionNode' => 'effect',
|
156
|
+
'org.graalvm.compiler.nodes.java.ArrayLengthNode' => 'effect',
|
157
|
+
'org.graalvm.compiler.nodes.java.LoadFieldNode' => 'effect',
|
158
|
+
'org.graalvm.compiler.nodes.java.LoadIndexedNode' => 'effect',
|
159
|
+
'org.graalvm.compiler.nodes.java.MonitorEnterNode' => 'effect',
|
160
|
+
'org.graalvm.compiler.nodes.java.MonitorExitNode' => 'effect',
|
161
|
+
'org.graalvm.compiler.nodes.java.NewArrayNode' => 'effect',
|
162
|
+
'org.graalvm.compiler.nodes.java.NewInstanceNode' => 'effect',
|
163
|
+
'org.graalvm.compiler.nodes.java.RawMonitorEnterNode' => 'effect',
|
164
|
+
'org.graalvm.compiler.nodes.java.StoreFieldNode' => 'effect',
|
165
|
+
'org.graalvm.compiler.nodes.java.StoreIndexedNode' => 'effect',
|
166
|
+
'org.graalvm.compiler.nodes.KillingBeginNode' => 'control',
|
167
|
+
'org.graalvm.compiler.nodes.LoopBeginNode' => 'control',
|
168
|
+
'org.graalvm.compiler.nodes.LoopEndNode' => 'control',
|
169
|
+
'org.graalvm.compiler.nodes.LoopExitNode' => 'control',
|
170
|
+
'org.graalvm.compiler.nodes.memory.ReadNode' => 'effect',
|
171
|
+
'org.graalvm.compiler.nodes.memory.WriteNode' => 'effect',
|
172
|
+
'org.graalvm.compiler.nodes.MergeNode' => 'control',
|
173
|
+
'org.graalvm.compiler.nodes.ParameterNode' => 'input',
|
174
|
+
'org.graalvm.compiler.nodes.PrefetchAllocateNode' => 'effect',
|
175
|
+
'org.graalvm.compiler.nodes.ReturnNode' => 'control',
|
176
|
+
'org.graalvm.compiler.nodes.StartNode' => 'control',
|
177
|
+
'org.graalvm.compiler.nodes.UnwindNode' => 'effect',
|
178
|
+
'org.graalvm.compiler.nodes.virtual.AllocatedObjectNode' => 'virtual',
|
179
|
+
'org.graalvm.compiler.nodes.virtual.CommitAllocationNode' => 'effect',
|
180
|
+
'org.graalvm.compiler.nodes.virtual.VirtualArrayNode' => 'virtual',
|
181
|
+
'org.graalvm.compiler.nodes.VirtualObjectState' => 'info',
|
182
|
+
'org.graalvm.compiler.replacements.nodes.ArrayEqualsNode' => 'effect',
|
183
|
+
'org.graalvm.compiler.replacements.nodes.ReadRegisterNode' => 'effect',
|
184
|
+
'org.graalvm.compiler.replacements.nodes.WriteRegisterNode' => 'effect'
|
185
|
+
|
186
|
+
# org.graalvm.compiler.word.WordCastNode is not an effect even though it is fixed.
|
187
|
+
}
|
188
|
+
|
189
|
+
# Render a Graal 'name template'.
|
190
|
+
def render_name_template(template, node)
|
191
|
+
# {p#name} is replaced with the value of the name property. {i#name} is
|
192
|
+
# replaced with the ids of input nodes with edges named name. We
|
193
|
+
# probably need to do these replacements in one pass...
|
194
|
+
string = template
|
195
|
+
string = string.gsub(%r{\{p#(\w+)(/s)?\}}) { |_| node.props[Regexp.last_match(1)] }
|
196
|
+
string = string.gsub(%r{\{i#(\w+)(/s)?\}}) { |_| node.inputs.select { |e| e.props[:name] == Regexp.last_match(1) }.map { |e| e.from.id }.join(', ') }
|
197
|
+
string = string.gsub(/\{x#field\}/) { |_| "#{node.props.dig('field', :field_class).split('.').last}.#{node.props.dig('field', :name)}" }
|
198
|
+
string = string.gsub(/\{x#state\}/) { |_| "#{node.props.dig('code', :declaring_class)}##{node.props.dig('code', :method_name)} #{node.props['sourceFile']}:#{node.props['sourceLine']}" }
|
199
|
+
string = string.gsub(/\{x#simpleStamp\}/) do |_|
|
200
|
+
stamp = node.props.dig('checkedStamp')
|
201
|
+
if stamp =~ /a!?# L(.*);/
|
202
|
+
Regexp.last_match(1)
|
203
|
+
else
|
204
|
+
stamp
|
205
|
+
end
|
206
|
+
end
|
207
|
+
string
|
208
|
+
end
|
209
|
+
|
210
|
+
# Annotate edges with their label and kind.
|
211
|
+
def annotate_edges(graph)
|
212
|
+
graph.edges.each do |edge|
|
213
|
+
if edge.to.props.dig(:node_class, :node_class) == 'org.graalvm.compiler.nodes.ValuePhiNode' && edge.props[:name] == 'values'
|
214
|
+
merge_node = edge.to.edges.find { |e| e.props[:name] == 'merge' }.from
|
215
|
+
control_into_merge = %w[ends loopBegin]
|
216
|
+
merge_node_control_edges_in = merge_node.edges.select { |e| control_into_merge.include?(e.props[:name]) && e.to.props.dig(:node_class, :node_class) != 'org.graalvm.compiler.nodes.LoopExitNode' }
|
217
|
+
matching_control_edge = merge_node_control_edges_in[edge.props[:index]]
|
218
|
+
control_in_node = matching_control_edge.nodes.find { |n| n != merge_node }
|
219
|
+
edge.props[:label] = "from #{control_in_node.id}"
|
220
|
+
edge.props[:kind] = 'data'
|
221
|
+
next
|
222
|
+
end
|
223
|
+
|
224
|
+
# Look at the name of the edge to decide how to treat them.
|
225
|
+
case edge.props[:name]
|
226
|
+
|
227
|
+
# Control edges.
|
228
|
+
when 'ends', 'next', 'trueSuccessor', 'falseSuccessor', 'exceptionEdge'
|
229
|
+
edge.props[:kind] = 'control'
|
230
|
+
case edge.props[:name]
|
231
|
+
when 'trueSuccessor'
|
232
|
+
# Simplify trueSuccessor to T
|
233
|
+
edge.props[:label] = 'T'
|
234
|
+
when 'falseSuccessor'
|
235
|
+
# Simplify falseSuccessor to F
|
236
|
+
edge.props[:label] = 'F'
|
237
|
+
when 'exceptionEdge'
|
238
|
+
# Simplify exceptionEdge to unwind
|
239
|
+
edge.props[:label] = 'unwind'
|
240
|
+
end
|
241
|
+
|
242
|
+
# Info edges, which are drawn reversed as they point from the user
|
243
|
+
# to the info.
|
244
|
+
when 'frameState', 'callTarget', 'stateAfter'
|
245
|
+
edge.props[:label] = nil
|
246
|
+
edge.props[:kind] = 'info'
|
247
|
+
edge.props[:reverse] = true
|
248
|
+
|
249
|
+
# Condition for branches, which we label as ?.
|
250
|
+
when 'condition'
|
251
|
+
edge.props[:kind] = 'data'
|
252
|
+
edge.props[:label] = '?'
|
253
|
+
|
254
|
+
# loopBegin edges point from LoopEndNode (continue) and LoopExitNode
|
255
|
+
# (break) to the LoopBeginNode. Both are drawn reversed.
|
256
|
+
when 'loopBegin'
|
257
|
+
edge.props[:hidden] = true
|
258
|
+
|
259
|
+
case edge.to.props.dig(:node_class, :node_class)
|
260
|
+
when 'org.graalvm.compiler.nodes.LoopEndNode'
|
261
|
+
# If it's from the LoopEnd then it's the control edge to follow.
|
262
|
+
edge.props[:kind] = 'loop'
|
263
|
+
edge.props[:reverse] = true
|
264
|
+
when 'org.graalvm.compiler.nodes.LoopExitNode'
|
265
|
+
# If it's from the LoopExit then it's just for information - it's
|
266
|
+
# not control flow to follow.
|
267
|
+
edge.props[:kind] = 'info'
|
268
|
+
edge.props[:reverse] = true
|
269
|
+
else
|
270
|
+
raise EncodingError, 'loopBegin edge not to LoopEnd or LoopExit'
|
271
|
+
end
|
272
|
+
|
273
|
+
# Merges are info.
|
274
|
+
when 'merge'
|
275
|
+
edge.props[:kind] = 'info'
|
276
|
+
edge.props[:reverse] = true
|
277
|
+
|
278
|
+
# Successors are control from a switch.
|
279
|
+
when 'successors'
|
280
|
+
# We want to label the edges with the corresponding index of the key.
|
281
|
+
if edge.props[:index] == edge.from.props['keys'].size
|
282
|
+
label = 'default'
|
283
|
+
else
|
284
|
+
label = "k[#{edge.props[:index]}]"
|
285
|
+
end
|
286
|
+
edge.props[:label] = label
|
287
|
+
edge.props[:kind] = 'control'
|
288
|
+
|
289
|
+
# Successors are control from a switch.
|
290
|
+
when 'arguments'
|
291
|
+
# We want to label the edges with their corresponding argument index.
|
292
|
+
edge.props[:label] = "arg[#{edge.props[:index]}]"
|
293
|
+
edge.props[:kind] = 'data'
|
294
|
+
|
295
|
+
# Everything else is data.
|
296
|
+
else
|
297
|
+
edge.props[:kind] = 'data'
|
298
|
+
edge.props[:label] = edge.props[:name]
|
299
|
+
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# Hide frame state nodes. These are for deoptimisation, are rarely
|
305
|
+
# useful to look at, and severely clutter the graph.
|
306
|
+
def hide_frame_state(graph)
|
307
|
+
graph.nodes.each_value do |node|
|
308
|
+
if FRAME_STATE_NODES.include?(node.props.dig(:node_class, :node_class))
|
309
|
+
node.props[:hidden] = true
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Hide floating nodes. This highlights just the control flow backbone.
|
315
|
+
def hide_floating(graph)
|
316
|
+
graph.nodes.each_value do |node|
|
317
|
+
if node.edges.none? { |e| e.props[:kind] == 'control' }
|
318
|
+
node.props[:hidden] = true
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
# Reduce edges to simple constants and parameters by inlining them. An
|
324
|
+
# inlined node is one that is shown each time it is used, just above the
|
325
|
+
# using node. This reduces very long edges all across the graph. We inline
|
326
|
+
# nodes that are 'simple', like parameters and constants.
|
327
|
+
def reduce_edges(graph)
|
328
|
+
graph.nodes.each_value do |node|
|
329
|
+
if SIMPLE_INPUTS.include?(node.props.dig(:node_class, :node_class))
|
330
|
+
node.props[:inlined] = true
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
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.
|
339
|
+
def hide_unused_nodes(graph)
|
340
|
+
loop do
|
341
|
+
modified = false
|
342
|
+
graph.nodes.each_value do |node|
|
343
|
+
next unless node.outputs.all? { |edge| edge.to.props[:hidden] } &&
|
344
|
+
node.inputs.none? { |edge| edge.props[:kind] == 'control' } &&
|
345
|
+
node.inputs.none? { |edge| edge.props[:name] == 'anchor' }
|
346
|
+
|
347
|
+
unless node.props[:hidden]
|
348
|
+
node.props[:hidden] = true
|
349
|
+
modified = true
|
350
|
+
end
|
351
|
+
end
|
352
|
+
break unless modified
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
# If we see these in the graph properties it's probably a Graal graph.
|
357
|
+
TRIGGERS = %w[
|
358
|
+
HostedGraphBuilderPhase
|
359
|
+
GraalCompiler
|
360
|
+
TruffleCompilerThread
|
361
|
+
]
|
362
|
+
|
363
|
+
# Simple input node classes that may be inlined.
|
364
|
+
SIMPLE_INPUTS = %w[
|
365
|
+
org.graalvm.compiler.nodes.ConstantNode
|
366
|
+
org.graalvm.compiler.nodes.ParameterNode
|
367
|
+
]
|
368
|
+
|
369
|
+
# Nodes just to maintain frame state.
|
370
|
+
FRAME_STATE_NODES = %w[
|
371
|
+
org.graalvm.compiler.nodes.FrameState
|
372
|
+
org.graalvm.compiler.virtual.nodes.MaterializedObjectState
|
373
|
+
]
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|