seafoam 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.github/probots.yml +2 -0
  3. data/.github/workflows/rubocop.yml +10 -0
  4. data/.github/workflows/specs.yml +19 -0
  5. data/.gitignore +7 -0
  6. data/.rubocop.yml +34 -0
  7. data/.ruby-version +1 -0
  8. data/.seafoam/config +1 -0
  9. data/CODE_OF_CONDUCT.md +128 -0
  10. data/CONTRIBUTING.md +5 -0
  11. data/Gemfile +2 -0
  12. data/LICENSE.md +7 -0
  13. data/README.md +298 -0
  14. data/bin/bgv2isabelle +53 -0
  15. data/bin/bgv2json +42 -0
  16. data/bin/seafoam +24 -0
  17. data/docs/annotators.md +43 -0
  18. data/docs/bgv.md +284 -0
  19. data/docs/getting-graphs.md +47 -0
  20. data/examples/Fib.java +24 -0
  21. data/examples/MatMult.java +39 -0
  22. data/examples/fib-java.bgv +0 -0
  23. data/examples/fib-js.bgv +0 -0
  24. data/examples/fib-ruby.bgv +0 -0
  25. data/examples/fib.js +15 -0
  26. data/examples/fib.rb +15 -0
  27. data/examples/identity.bgv +0 -0
  28. data/examples/identity.rb +13 -0
  29. data/examples/java/Irreducible.j +35 -0
  30. data/examples/java/IrreducibleDecompiled.java +21 -0
  31. data/examples/java/JavaExamples.java +418 -0
  32. data/examples/java/exampleArithOperator.bgv +0 -0
  33. data/examples/java/exampleArithOperator.cfg +925 -0
  34. data/examples/java/exampleArrayAllocation.bgv +0 -0
  35. data/examples/java/exampleArrayAllocation.cfg +5268 -0
  36. data/examples/java/exampleArrayRead.bgv +0 -0
  37. data/examples/java/exampleArrayRead.cfg +2263 -0
  38. data/examples/java/exampleArrayWrite.bgv +0 -0
  39. data/examples/java/exampleArrayWrite.cfg +2315 -0
  40. data/examples/java/exampleCatch.bgv +0 -0
  41. data/examples/java/exampleCatch.cfg +4150 -0
  42. data/examples/java/exampleCompareOperator.bgv +0 -0
  43. data/examples/java/exampleCompareOperator.cfg +1109 -0
  44. data/examples/java/exampleDoubleSynchronized.bgv +0 -0
  45. data/examples/java/exampleDoubleSynchronized.cfg +26497 -0
  46. data/examples/java/exampleExactArith.bgv +0 -0
  47. data/examples/java/exampleExactArith.cfg +1888 -0
  48. data/examples/java/exampleFieldRead.bgv +0 -0
  49. data/examples/java/exampleFieldRead.cfg +1228 -0
  50. data/examples/java/exampleFieldWrite.bgv +0 -0
  51. data/examples/java/exampleFieldWrite.cfg +1102 -0
  52. data/examples/java/exampleFor.bgv +0 -0
  53. data/examples/java/exampleFor.cfg +3936 -0
  54. data/examples/java/exampleFullEscape.bgv +0 -0
  55. data/examples/java/exampleFullEscape.cfg +5893 -0
  56. data/examples/java/exampleIf.bgv +0 -0
  57. data/examples/java/exampleIf.cfg +2462 -0
  58. data/examples/java/exampleIfNeverTaken.bgv +0 -0
  59. data/examples/java/exampleIfNeverTaken.cfg +2476 -0
  60. data/examples/java/exampleInstanceOfManyImpls.bgv +0 -0
  61. data/examples/java/exampleInstanceOfManyImpls.cfg +6391 -0
  62. data/examples/java/exampleInstanceOfOneImpl.bgv +0 -0
  63. data/examples/java/exampleInstanceOfOneImpl.cfg +2604 -0
  64. data/examples/java/exampleIntSwitch.bgv +0 -0
  65. data/examples/java/exampleIntSwitch.cfg +3121 -0
  66. data/examples/java/exampleInterfaceCallManyImpls.bgv +0 -0
  67. data/examples/java/exampleInterfaceCallManyImpls.cfg +1358 -0
  68. data/examples/java/exampleInterfaceCallOneImpl.bgv +0 -0
  69. data/examples/java/exampleInterfaceCallOneImpl.cfg +3859 -0
  70. data/examples/java/exampleLocalInstanceOf.bgv +0 -0
  71. data/examples/java/exampleLocalInstanceOf.cfg +5276 -0
  72. data/examples/java/exampleLocalSynchronized.bgv +0 -0
  73. data/examples/java/exampleLocalSynchronized.cfg +1364 -0
  74. data/examples/java/exampleLocalVariables.bgv +0 -0
  75. data/examples/java/exampleLocalVariables.cfg +1195 -0
  76. data/examples/java/exampleLocalVariablesState.bgv +0 -0
  77. data/examples/java/exampleLocalVariablesState.cfg +1673 -0
  78. data/examples/java/exampleNestedWhile.bgv +0 -0
  79. data/examples/java/exampleNestedWhile.cfg +15499 -0
  80. data/examples/java/exampleNestedWhileBreak.bgv +0 -0
  81. data/examples/java/exampleNestedWhileBreak.cfg +11162 -0
  82. data/examples/java/exampleNoEscape.bgv +0 -0
  83. data/examples/java/exampleNoEscape.cfg +974 -0
  84. data/examples/java/exampleObjectAllocation.bgv +0 -0
  85. data/examples/java/exampleObjectAllocation.cfg +5287 -0
  86. data/examples/java/examplePartialEscape.bgv +0 -0
  87. data/examples/java/examplePartialEscape.cfg +7042 -0
  88. data/examples/java/examplePhi.bgv +0 -0
  89. data/examples/java/examplePhi.cfg +3227 -0
  90. data/examples/java/exampleReducible.bgv +0 -0
  91. data/examples/java/exampleReducible.cfg +5578 -0
  92. data/examples/java/exampleSimpleCall.bgv +0 -0
  93. data/examples/java/exampleSimpleCall.cfg +1435 -0
  94. data/examples/java/exampleStamp.bgv +0 -0
  95. data/examples/java/exampleStamp.cfg +913 -0
  96. data/examples/java/exampleStaticCall.bgv +0 -0
  97. data/examples/java/exampleStaticCall.cfg +1154 -0
  98. data/examples/java/exampleStringSwitch.bgv +0 -0
  99. data/examples/java/exampleStringSwitch.cfg +15377 -0
  100. data/examples/java/exampleSynchronized.bgv +0 -0
  101. data/examples/java/exampleSynchronized.cfg +26027 -0
  102. data/examples/java/exampleThrow.bgv +0 -0
  103. data/examples/java/exampleThrow.cfg +780 -0
  104. data/examples/java/exampleThrowCatch.bgv +0 -0
  105. data/examples/java/exampleThrowCatch.cfg +744 -0
  106. data/examples/java/exampleUnsafeRead.bgv +0 -0
  107. data/examples/java/exampleUnsafeRead.cfg +912 -0
  108. data/examples/java/exampleUnsafeWrite.bgv +0 -0
  109. data/examples/java/exampleUnsafeWrite.cfg +962 -0
  110. data/examples/java/exampleWhile.bgv +0 -0
  111. data/examples/java/exampleWhile.cfg +3936 -0
  112. data/examples/java/exampleWhileBreak.bgv +0 -0
  113. data/examples/java/exampleWhileBreak.cfg +5963 -0
  114. data/examples/matmult-java.bgv +0 -0
  115. data/examples/matmult-ruby.bgv +0 -0
  116. data/examples/matmult.rb +29 -0
  117. data/examples/overflow.bgv +0 -0
  118. data/examples/overflow.rb +13 -0
  119. data/lib/seafoam.rb +13 -0
  120. data/lib/seafoam/annotators.rb +54 -0
  121. data/lib/seafoam/annotators/fallback.rb +27 -0
  122. data/lib/seafoam/annotators/graal.rb +376 -0
  123. data/lib/seafoam/bgv/bgv_parser.rb +602 -0
  124. data/lib/seafoam/binary/binary_reader.rb +21 -0
  125. data/lib/seafoam/binary/io_binary_reader.rb +88 -0
  126. data/lib/seafoam/colors.rb +18 -0
  127. data/lib/seafoam/commands.rb +447 -0
  128. data/lib/seafoam/config.rb +34 -0
  129. data/lib/seafoam/graph.rb +91 -0
  130. data/lib/seafoam/graphviz_writer.rb +213 -0
  131. data/lib/seafoam/spotlight.rb +28 -0
  132. data/lib/seafoam/version.rb +5 -0
  133. data/seafoam.gemspec +20 -0
  134. data/spec/seafoam/annotators/fallback_spec.rb +69 -0
  135. data/spec/seafoam/annotators/graal_spec.rb +96 -0
  136. data/spec/seafoam/annotators_spec.rb +61 -0
  137. data/spec/seafoam/bgv/bgv_parser_spec.rb +144 -0
  138. data/spec/seafoam/bgv/fixtures/not.bgv +1 -0
  139. data/spec/seafoam/bgv/fixtures/unsupported.bgv +1 -0
  140. data/spec/seafoam/binary/io_binary_reader_spec.rb +176 -0
  141. data/spec/seafoam/command_spec.rb +252 -0
  142. data/spec/seafoam/graph_spec.rb +172 -0
  143. data/spec/seafoam/graphviz_writer_spec.rb +63 -0
  144. data/spec/seafoam/spec_helpers.rb +30 -0
  145. data/spec/seafoam/spotlight_spec.rb +38 -0
  146. data/tools/render-all +36 -0
  147. metadata +238 -0
Binary file
Binary file
@@ -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
@@ -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