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