Seafoam is a tool for working with compiler graphs dumped by the GraalVM compiler, including the Graal JIT, Native Image, and Truffle.
The Ideal Graph Visualizer, or IGV, is the tool usually used to work with GraalVM compiler graphs. Seafoam aims to solve several problems with IGV. Unlike IGV, Seafoam:
Admittedly, Seafoam does not yet have:
% brew install graphviz
% gem install seafoam
% seafoam --version
seafoam 0.1
% sudo apt-get install ruby graphviz
% gem install seafoam
% seafoam --version
seafoam 0.1
% sudo yum install ruby graphviz
% gem install seafoam
% seafoam --version
seafoam 0.1
% seafoam examples/fib-java.bgv.gz:0 render
If you are just experimenting, there are example graphs such as
examples/fib-java.bgv.gz
.
This is just a quick summary - see more information on getting graphs out of compilers.
% javac Fib.java
% java -XX:CompileOnly=::fib -Dgraal.Dump=:1 Fib 14
% native-image -H:Dump=:1 -H:MethodFilter=fib Fib
% ruby --experimental-options --engine.CompileOnly=fib --engine.Inlining=false --engine.OSR=false --vm.Dgraal.Dump=Truffle:1 fib.rb 14
You will usually want to look at the After TruffleTier graph.
When using a command-line interface, Seafoam refers to
file.bgv[:graph][:node[-edge]]
, where file.bgv
is a file, graph
is a graph
index, node
is a node index, and to
is another node index to form an edge
from node
to another node edge
.
Note that a graph ID is an ID found in BGV files, but is not unique. A graph index is what we use in names, and is unique.
% seafoam examples/fib-java.bgv.gz info
BGV 7.0
To simplify automation use cases, you can also capture the output as JSON:
% seafoam --json examples/fib-java.bgv.gz info | jq --sort-keys
{
"major_version": 7,
"minor_version": 0
}
% seafoam examples/fib-java.bgv.gz list
examples/fib-java.bgv.gz:0 17:Fib.fib(int)/After parsing
examples/fib-java.bgv.gz:1 17:Fib.fib(int)/Before phase org.graalvm.compiler.phases.common.LoweringPhase
examples/fib-java.bgv.gz:2 17:Fib.fib(int)/After high tier
examples/fib-java.bgv.gz:3 17:Fib.fib(int)/After mid tier
examples/fib-java.bgv.gz:4 17:Fib.fib(int)/After low tier
...
To simplify automation use cases, you can also capture the output as JSON:
% seafoam --json examples/fib-java.bgv.gz list | jq --sort-keys
[
{
"graph_file": "examples/fib-java.bgv.gz",
"graph_index": 0,
"graph_name_components": [
"17:Fib.fib(int)",
"After parsing"
]
},
{
"graph_file": "examples/fib-java.bgv.gz",
"graph_index": 1,
"graph_name_components": [
"17:Fib.fib(int)",
"Before phase org.graalvm.compiler.phases.common.LoweringPhase"
]
},
...
]
% seafoam examples/fib-java.bgv.gz:0 search Start
examples/fib-java.bgv.gz:0:0 ...node_class":"org.graalvm.compiler.nodes.StartNode","name_template":"Start","inputs":[...
examples/fib-java.bgv.gz:0:0 ...piler.nodes.StartNode","name_template":"Start","inputs":[{"direct":true,"name":"state...
NB: This command is intended for interactive usage and as such, has no JSON output option.
% seafoam examples/fib-java.bgv.gz:0 edges
21 nodes, 30 edges
% seafoam examples/fib-java.bgv.gz:0:13 edges
Input:
13 (Call Fib.fib) <-() 6 (Begin)
13 (Call Fib.fib) <-() 14 (FrameState Fib#fib Fib.java:20)
13 (Call Fib.fib) <-() 12 (MethodCallTarget)
Output:
13 (Call Fib.fib) ->() 18 (Call Fib.fib)
13 (Call Fib.fib) ->(values) 14 (FrameState Fib#fib Fib.java:20)
13 (Call Fib.fib) ->(values) 19 (FrameState Fib#fib Fib.java:20)
13 (Call Fib.fib) ->(x) 20 (+)
% seafoam examples/fib-java.bgv.gz:0:13-20 edges
13 (Call Fib.fib) ->(x) 20 (+)
To simplify automation use cases, you can also capture the output as JSON:
% seafoam --json examples/fib-java.bgv.gz:0 edges | jq --sort-keys
{
"edge_count": 30,
"node_count": 21
}
seafoam --json examples/fib-java.bgv.gz:0 edges | jq --sort-keys
{
"edge_count": 30,
"node_count": 21
}
% seafoam --json examples/fib-java.bgv.gz:0:13 edges | jq --sort-keys
{
"input": [
{
"from": {
"id": "6",
"label": "Begin"
},
"label": null,
"to": {
"id": "13",
"label": "Call Fib.fib"
}
},
...
],
"output": [
{
"from": {
"id": "13",
"label": "Call Fib.fib"
},
"label": null,
"to": {
"id": "18",
"label": "Call Fib.fib"
}
},
...
]
}
% seafoam --json examples/fib-java.bgv.gz:0:13-20 edges | jq --sort-keys
[
{
"from": {
"id": "13",
"label": "Call Fib.fib"
},
"label": "x",
"to": {
"id": "20",
"label": "+"
}
}
]
% seafoam examples/fib-java.bgv.gz:0 props
{
"group": [
{
"name": "17:Fib.fib(int)",
"short_name": "17:Fib.fib(int)",
"method": null,
...
% seafoam examples/fib-java.bgv.gz:0:13 props
{
"relativeFrequency": 0.4995903151404586,
"targetMethod": "Fib.fib",
"nodeCostSize": "SIZE_2",
"stamp": "i32",
"bci": 10,
"polymorphic": false,
...
% seafoam examples/fib-java.bgv.gz:0:13-20 props
{
"direct": true,
"name": "x",
"type": "Value",
"index": 0
}
NB: The output from the props
command is always in JSON so there is no need to supply the --json
flag.
For Truffle graphs you need to run with --engine.NodeSourcePositions
to get
useful source information. This only works on JVM or on Native when built with
-H:+IncludeNodeSourcePositions
, which isn't set by default.
% seafoam examples/fib-ruby.bgv.gz:2:2436 source
java.lang.Math#addExact
org.truffleruby.core.numeric.IntegerNodes$AddNode#add
org.truffleruby.core.numeric.IntegerNodesFactory$AddNodeFactory$AddNodeGen#executeAdd
org.truffleruby.core.inlined.InlinedAddNode#intAdd
org.truffleruby.core.inlined.InlinedAddNodeGen#execute
org.truffleruby.language.control.IfElseNode#execute
org.truffleruby.language.control.SequenceNode#execute
org.truffleruby.language.RubyMethodRootNode#execute
org.graalvm.compiler.truffle.runtime.OptimizedCallTarget#executeRootNode
org.graalvm.compiler.truffle.runtime.OptimizedCallTarget#profiledPERoot
To simplify automation use cases, you can also capture the output as JSON:
% seafoam --json examples/fib-ruby.bgv.gz:2:2436 source | jq --sort-keys
[
{
"class": "java.lang.Math",
"method": "addExact"
},
{
"class": "org.truffleruby.core.numeric.IntegerNodes$AddNode",
"method": "add"
},
{
"class": "org.truffleruby.core.numeric.IntegerNodesFactory$AddNodeFactory$AddNodeGen",
"method": "executeAdd"
},
{
"class": "org.truffleruby.core.inlined.InlinedAddNode",
"method": "intAdd"
},
{
"class": "org.truffleruby.core.inlined.InlinedAddNodeGen",
"method": "execute"
},
{
"class": "org.truffleruby.language.control.IfElseNode",
"method": "execute"
},
{
"class": "org.truffleruby.language.control.SequenceNode",
"method": "execute"
},
{
"class": "org.truffleruby.language.RubyMethodRootNode",
"method": "execute"
},
{
"class": "org.graalvm.compiler.truffle.runtime.OptimizedCallTarget",
"method": "executeRootNode"
},
{
"class": "org.graalvm.compiler.truffle.runtime.OptimizedCallTarget",
"method": "profiledPERoot"
}
]
Describe the key features of a graph without rendering it.
% seafoam examples/fib-java.bgv.gz:1 describe
21 nodes, branches, calls
The graph description is a useful way to quickly catch unexpected or undesired attributes in a graph and makes comparing two graphs to each other straightforward. Such a comparison could be the basis of a regression test.
To simplify automation use cases, you can also capture the output as JSON:
% seafoam --json examples/fib-java.bgv.gz:1 describe | jq
{
"branches": true,
"calls": true,
"deopts": false,
"linear": false,
"loops": false,
"node_count": 21,
"node_counts": {
"AddNode": 3,
"ConstantNode": 3,
"FrameState": 3,
"BeginNode": 2,
"InvokeNode": 2,
"MethodCallTargetNode": 2,
"ReturnNode": 2,
"IfNode": 1,
"IntegerLessThanNode": 1,
"ParameterNode": 1,
"StartNode": 1
}
}
Render a graph as a PDF image and have it opened automatically.
% seafoam examples/fib-java.bgv.gz:0 render
Render a graph showing just a few nodes and those surrounding them, similar to the IGV feature of gradually revealing nodes.
% seafoam examples/fib-java.bgv.gz:0 render --spotlight 13,20
render
supports these options:
--out filename.pdf
or .pdf
, .svg
, .png
, .dot
, .mmd
, .md
--md
--option key value
for pass options.Convert a BGV file to the Isabelle graph format.
% bgv2isabelle examples/fib-java.bgv.gz
graph0 = # 2:Fib.fib(int)/After phase org.graalvm.compiler.java.GraphBuilderPhase
(add_node 0 StartNode [2] [8]
(add_node 1 (ParameterNode 0) [] [2, 5, 9, 11, 14, 16]
(add_node 2 FrameState [1] [0]
(add_node 3 (ConstantNode 1) [] []
(add_node 4 (ConstantNode 2) [] [5]
(add_node 5 IntegerLessThanNode [1, 4] [8]
(add_node 6 BeginNode [8] [13]
(add_node 7 BeginNode [8] [9]
(add_node 8 IfNode [0, 5] [7, 6]
...
Convert a BGV file to JSON.
% bgv2json examples/fib-java.bgv.gz
graph0 = # 2:Fib.fib(int)/After phase org.graalvm.compiler.java.GraphBuilderPhase
(add_node 0 StartNode [2] [8]
(add_node 1 (ParameterNode 0) [] [2, 5, 9, 11, 14, 16]
(add_node 2 FrameState [1] [0]
(add_node 3 (ConstantNode 1) [] []
(add_node 4 (ConstantNode 2) [] [5]
(add_node 5 IntegerLessThanNode [1, 4] [8]
(add_node 6 BeginNode [8] [13]
(add_node 7 BeginNode [8] [9]
(add_node 8 IfNode [0, 5] [7, 6]
...
--full-truffle-args
shows full Truffle argument nodes, which are simplified by default--show-frame-state
shows frame state nodes, which are hidden by default--no-simplify-alloc
turns off the pass to create synthetic allocation nodes--show-reachability-fences
turns off the pass to hide reachability fences--show-null-fields
shows null fields to allocations, which are hidden by default--show-pi
shows pi nodes, which are hidden by default--show-begin-end
shows begin and end nodes, which are hidden by default--hide-floating
hides nodes that aren't fixed by control flow--no-reduce-edges
turns off the pass to reduce the number of edges by inlining simple nodes above their users--no-simplify
turns off passes that simplify the graph, except frame states are still hidden--draw-blocks
to draw basic block information if availableException backtraces are printed if $DEBUG
(-d
) is set.
Use seafoam file.bgv debug
to debug file parsing.
NB: This command is intended for interactive usage and as such, has no JSON output option.
GraalVM graphs are seas of nodes. Seafoam is a shade of green, and Seafoam was written at Shopify, which has green as a brand colour. Graphs can sometimes be very complicated, appearing like a foam without any structure - Seafoam tries to help you make sense of it all.
Graphs, as in edges and nodes, are the data structure some compilers use to represent your program while they're compiling it. It's a form of intermediate representation. Graphs are how the compiler understands the programs and if the compiler isn't doing what you want you need to look at the graph and make sense of it. Some graphs are loosely structured and large, making them like a sea or soup of nodes.
Yes, but they're shorter edges, and it achieves the intended effect of less edges crossing over the graph.
The graph layout algorithm we use, via Graphviz, is
IGV is the existing tool for working with Graal graphs. It uses a hierarchical layout algorithm, rather than the force directed algorithm we use when we use Graphviz. It's based on the NetBeans IDE platform. It's related to the C1 Visualiser, for control-flow graphs. The C1 Visualiser can also be used with Graal as the backend of Graal is similar enough to C1. IGV is closed-source and available under a proprietary licence.
Turbolizer is a similar tool for the intermediate representation in the V8 JavaScript compiler.
Seafoam was written by Chris Seaton at Shopify, chris.seaton@shopify.com.
MIT