seafoam 0.2
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.
- 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
|