compiler/docs/Debugging.md
This pages covers the various mechanisms currently available for debugging the GraalVM compiler.
All the parts of the GraalVM compiler (henceforth just "compiler") written in Java can be debugged with a standard Java debugger. While debugging with Eclipse is described here, it should not be too hard to adapt these instructions for another debugger.
The mx eclipseinit command not only creates Eclipse project configurations but also creates an
Eclipse launch configuration (in
mx.compiler/eclipse-launches/compiler-attach-localhost-8000.launch) that can be used to debug all
compiler code running in the VM. This launch configuration requires you to start the VM with the -d
global option which puts the VM into a state waiting for a remote debugger to attach to port 8000:
mx -d vm -XX:+UseJVMCICompiler -version
Listening for transport dt_socket at address: 8000
Note that the -d option applies to any command that runs the VM, such as the mx unittest command:
mx -d vm unittest
Listening for transport dt_socket at address: 8000
Once you see the message above, you then run the Eclipse launch configuration:
At this point you can set breakpoints and perform all other normal Java debugging actions.
In addition to IDE debugging, there is support for printf style debugging.
The simplest is to place temporary usages of System.out where ever you need them.
For more permanent logging statements, use the log(...) methods in
DebugContext.
A (nestable) debug scope is entered via one of the scope(...) methods in that class. For example:
DebugContext debug = ...;
InstalledCode code = null;
try (Scope s = debug.scope("CodeInstall", method)) {
code = ...
debug.log("installed code for %s", method);
} catch (Throwable e) {
throw debug.handle(e);
}
The debug.log statement will send output to the console if CodeInstall is matched by the
-Djdk.graal.Log option. The matching logic for this option is implemented in
DebugFilter
and documented in the
Dump help message. As
mentioned in the javadoc, the same matching logic also applies to the -Djdk.graal.Dump,
-Djdk.graal.Time, -Djdk.graal.Count and -Djdk.graal.TrackMemUse options.
Since DebugContext objects are thread local, they need to be passed around as parameters. For
convenience, they may be available from objects such as a Graph or a Node but care needs to be
taken when such objects can be exposed to multiple threads. There are assertions in DebugContext
guarding against use in a thread different from the one in which it was instantiated.
JVMCI and GraalVM compiler options are specified by the jvmci.* and jdk.graal.* system properties
respectively. These must be specified on the JVM command line. Modifications to these properties by
application code are not seen by JVMCI and the compiler. A listing of all supported properties can be
obtained with -XX:+JVMCIPrintProperties -Djdk.graal.PrintPropertiesAll=true.
The compiler supports metrics in the form of counters, timers and memory trackers.
Each metric has a unique name. Metrics are collected per-compilation.
At shutdown, they are aggregated across all compilations and reported to the console.
This output can be redirected to a file via the -Djdk.graal.AggregatedMetricsFile option.
To list the per-compilation metrics, use the -Djdk.graal.MetricsFile option. If not specified,
per-compilation metrics are not reported.
Metrics are represented in the code via fields or variables of type CounterKey, TimerKey or MemUseTrackerKey.
As implied by the Key suffix, these objects do not store the value of a metric.
The value is stored in a DebugContext object. How to use each of these metrics is described below.
CounterKey counts some quantity.
For example:// declaration
private static final CounterKey ByteCodesCompiled = DebugContext.counter("ByteCodesCompiled");
// usage
DebugContext debug = ...;
long compiled = ... ;
ByteCodesCompiled.add(debug, compiled);
TimerKey records the time spent in a scope. For example:// declaration
private static final TimerKey CompilationTime = DebugContext.timer("CompilationTime");
// usage
DebgContext debug = ...;
try (DebugCloseable scope = CompilationTime.start(debug)) {
...
}
// time spent in the above scope will be added to the `CompilationTime` value in `debug`
When reporting a timer, two values are reported. The accumulated time represents the real time spent in the scope. The flat time is the amount of time spent within the scope minus the time spent in any nested timer scopes. For example:
CompilationTime_Accm=100.0 ms
CompilationTime_Flat=10.0 ms
MemUseTrackerKey tracks the memory allocation in a scope. For example:// declaration
private static final MemUseTrackerKey CompilationMemory = DebugContext.memUseTracker("CompilationMemory");
// usage
DebgContext debug = ...;
try (DebugCloseable a = CompilationMemory.start(debug)) {
...
}
// the bytes allocated in the above scope will be added to the `CompilationMemory` value in `debug`
Like TimerKey, an accumulated and flat value is reported for memory usage tracker.
For example:
CompilationMemory_Accm=100000 bytes
CompilationMemory_Flat=1000 bytes
For both the -Djdk.graal.AggregatedMetricsFile and the -Djdk.graal.MetricsFile option, the output format
is determined by the file name suffix. A .csv suffix produces a semi-colon separated format that
is amenable to automatic data processing.
The columns in the -Djdk.graal.MetricsFile CSV output are:
Example -Djdk.graal.MetricsFile output:
java.lang.String.hashCode()int;1272077530;0;HotSpotCompilation-95;PhaseNodes_PhaseSuite;1
java.lang.String.hashCode()int;1272077530;0;HotSpotCompilation-95;PhaseNodes_GraphBuilderPhase;1
java.lang.String.hashCode()int;1272077530;0;HotSpotCompilation-95;PhaseCount_GraphBuilderPhase;1
java.lang.String.hashCode()int;1272077530;0;HotSpotCompilation-95;PhaseMemUse_GraphBuilderPhase_Accm;896536
java.lang.String.hashCode()int;1272077530;0;HotSpotCompilation-95;PhaseMemUse_GraphBuilderPhase_Flat;896536
java.lang.String.hashCode()int;1272077530;0;HotSpotCompilation-95;PhaseTime_GraphBuilderPhase_Accm;31095000
java.lang.String.hashCode()int;1272077530;0;HotSpotCompilation-95;PhaseTime_GraphBuilderPhase_Flat;27724000
The columns in the -Djdk.graal.AggregatedMetricsFile CSV output are:
Example -Djdk.graal.AggregatedMetricsFile output:
AllocationsRemoved;6446;
BackEnd_Accm;7931303;us
BackEnd_Flat;91919;us
DuplicateGraph_Accm;1935356;us
DuplicateGraph_Flat;1935356;us
LIRPhaseMemUse_AllocationStage_Accm;499763160;bytes
LIRPhaseMemUse_AllocationStage_Flat;17354112;bytes
Note that -Djdk.graal.MetricsFile produces per-compilation output, not per-method output.
If a method is compiled multiple times, post-processing of the CSV output will be required
to obtain per-method metric values.
Specifying one of the debug scope options (i.e., -Djdk.graal.Log, -Djdk.graal.Dump, -Djdk.graal.Counter,
-Djdk.graal.Timer, or -Djdk.graal.MemUseTracker) can generate a lot of output. Typically, you are only
interesting in compiler output related to a single or few methods. In this case, use the
-Djdk.graal.MethodFilter option to specify a method filter. The matching logic for this option is
described in
MethodFilter
and documented in the
MethodFilter help message.
Alternatively, you may only want to see certain metrics. The -Djdk.graal.Timers, -Djdk.graal.Counters
and -Djdk.graal.MemUseTrackers track everything if nothing is specified. Else, these options take a comma separated list of
metrics names. Only the named metrics will be activated and reported. To see the available metric
names, specify -Djdk.graal.ListMetrics=true. At VM shutdown this option lists all the metrics that
were encountered during the VM execution. It does not list metrics registered on non-executed paths
since metric registration is lazy. For example, to see all the metrics available in a boot strap:
mx vm -XX:+UseJVMCICompiler -XX:+BootstrapJVMCI -Djdk.graal.ListMetrics=true -version
In addition to logging, there is support for generating (or dumping) more detailed visualizations of certain compiler data structures. Currently, there is support for dumping:
Dumping is enabled via the -Djdk.graal.Dump option. The dump handler for generating C1Visualizer
output will also generate output for LIR graphs if the -Djdk.graal.PrintBackendCFG=true option is specified
(in addition to the -Djdk.graal.Dump option).
By default, -Djdk.graal.Dump output is written to the local file system in a directory determined
by the -Djdk.graal.DumpPath option (default is $CWD/graal_dumps). You can send dumps directly to
the IGV over a network socket with the -Djdk.graal.PrintGraph=Network option. The -Djdk.graal.PrintGraphHost
and -Djdk.graal.PrintGraphPort options determine where the dumps are sent. By default, they are
sent to 127.0.0.1:4445 and IGV listens on port 4445 by default.
By default, the graph dumping code dumps every graph at a particular level even if the graph hasn't
changed since the previous dump. Internally the compiler tracks when changes are made to the edges
of the graph and setting the flag -Djdk.graal.PrintUnmodifiedGraphs=false will cause it to elide graph
dumps when no edges have changed since the previous dump. This can greatly reduce the number of
graphs dumped at high dump levels, though the resulting output can be a little harder to understand
without the context of the other graph names.
C1Visualizer output is written to *.cfg files. These can be opened via File -> Open Compiled Methods... in C1Visualizer.
The IGV can be launched with mx igv and the C1Visualizer can be launched via mx c1visualizer.
Various other VM options are of interest to see activity related to compilation:
-XX:+PrintCompilation or -Djdk.graal.PrintCompilation=true for notification and brief info about each compilation-XX:+TraceDeoptimization can be useful to see if excessive compilation is occurringTo see the compiler data structures used while compiling Node.updateUsages, use the following command:
> mx vm -XX:+UseJVMCICompiler -XX:+BootstrapJVMCI -XX:-TieredCompilation -Djdk.graal.Dump= -Djdk.graal.MethodFilter=Node.updateUsages -version
Bootstrapping JVMCI....Dumping debug output in /Users/dsimon/graal/graal/compiler/dumps/1497910458736
................................................ in 38177 ms (compiled 5206 methods)
java version "1.8.0_212"
Java(TM) SE Runtime Environment (build 1.8.0_212-b31)
Java HotSpot(TM) 64-Bit Server VM (build 25.212-b31-jvmci-20-b01, mixed mode)
> find dumps/1497910458736 -type f
dumps/1497910458736/HotSpotCompilation-539[jdk.graal.compiler.graph.Node.updateUsages(Node, Node)].bgv
dumps/1497910458736/HotSpotCompilation-539[jdk.graal.compiler.graph.Node.updateUsages(Node, Node)].cfg
As you become familiar with the scope names used in the compiler, you can refine the -Djdk.graal.Dump option
to limit the amount of dump output generated. For example, the "CodeGen" and "CodeInstall"
scopes are active during code generation and installation respectively. To see the machine code (in
the C1Visualizer) produced during these scopes:
mx vm -Djdk.graal.Dump=CodeGen,CodeInstall -Djdk.graal.MethodFilter=NodeClass.get -version
You will notice that no IGV output is generated by this command.
Alternatively, you can see the machine code using HotSpot's PrintAssembly support:
mx hsdis
mx vm -XX:+UseJVMCICompiler -XX:+BootstrapJVMCI -XX:-TieredCompilation -XX:CompileCommand='print,*Node.updateUsages' -version
The first step above installs the hsdis disassembler and only needs to be performed once.