8354520: IGV: dump contextual information

Reviewed-by: epeter, dfenacci
This commit is contained in:
Roberto Castañeda Lozano 2025-05-06 08:16:09 +00:00
parent 9f8fbf2922
commit def907ab89
7 changed files with 241 additions and 38 deletions

View File

@ -5205,32 +5205,39 @@ IdealGraphPrinter* Compile::_debug_network_printer = nullptr;
// Called from debugger. Prints method to the default file with the default phase name.
// This works regardless of any Ideal Graph Visualizer flags set or not.
void igv_print() {
Compile::current()->igv_print_method_to_file();
// Use in debugger (gdb/rr): p igv_print($sp, $fp, $pc).
void igv_print(void* sp, void* fp, void* pc) {
frame fr(sp, fp, pc);
Compile::current()->igv_print_method_to_file(nullptr, false, &fr);
}
// Same as igv_print() above but with a specified phase name.
void igv_print(const char* phase_name) {
Compile::current()->igv_print_method_to_file(phase_name);
void igv_print(const char* phase_name, void* sp, void* fp, void* pc) {
frame fr(sp, fp, pc);
Compile::current()->igv_print_method_to_file(phase_name, false, &fr);
}
// Called from debugger. Prints method with the default phase name to the default network or the one specified with
// the network flags for the Ideal Graph Visualizer, or to the default file depending on the 'network' argument.
// This works regardless of any Ideal Graph Visualizer flags set or not.
void igv_print(bool network) {
// Use in debugger (gdb/rr): p igv_print(true, $sp, $fp, $pc).
void igv_print(bool network, void* sp, void* fp, void* pc) {
frame fr(sp, fp, pc);
if (network) {
Compile::current()->igv_print_method_to_network();
Compile::current()->igv_print_method_to_network(nullptr, &fr);
} else {
Compile::current()->igv_print_method_to_file();
Compile::current()->igv_print_method_to_file(nullptr, false, &fr);
}
}
// Same as igv_print(bool network) above but with a specified phase name.
void igv_print(bool network, const char* phase_name) {
// Same as igv_print(bool network, ...) above but with a specified phase name.
// Use in debugger (gdb/rr): p igv_print(true, "MyPhase", $sp, $fp, $pc).
void igv_print(bool network, const char* phase_name, void* sp, void* fp, void* pc) {
frame fr(sp, fp, pc);
if (network) {
Compile::current()->igv_print_method_to_network(phase_name);
Compile::current()->igv_print_method_to_network(phase_name, &fr);
} else {
Compile::current()->igv_print_method_to_file(phase_name);
Compile::current()->igv_print_method_to_file(phase_name, false, &fr);
}
}
@ -5242,16 +5249,20 @@ void igv_print_default() {
// Called from debugger, especially when replaying a trace in which the program state cannot be altered like with rr replay.
// A method is appended to an existing default file with the default phase name. This means that igv_append() must follow
// an earlier igv_print(*) call which sets up the file. This works regardless of any Ideal Graph Visualizer flags set or not.
void igv_append() {
Compile::current()->igv_print_method_to_file("Debug", true);
// Use in debugger (gdb/rr): p igv_append($sp, $fp, $pc).
void igv_append(void* sp, void* fp, void* pc) {
frame fr(sp, fp, pc);
Compile::current()->igv_print_method_to_file(nullptr, true, &fr);
}
// Same as igv_append() above but with a specified phase name.
void igv_append(const char* phase_name) {
Compile::current()->igv_print_method_to_file(phase_name, true);
// Same as igv_append(...) above but with a specified phase name.
// Use in debugger (gdb/rr): p igv_append("MyPhase", $sp, $fp, $pc).
void igv_append(const char* phase_name, void* sp, void* fp, void* pc) {
frame fr(sp, fp, pc);
Compile::current()->igv_print_method_to_file(phase_name, true, &fr);
}
void Compile::igv_print_method_to_file(const char* phase_name, bool append) {
void Compile::igv_print_method_to_file(const char* phase_name, bool append, const frame* fr) {
const char* file_name = "custom_debug.xml";
if (_debug_file_printer == nullptr) {
_debug_file_printer = new IdealGraphPrinter(C, file_name, append);
@ -5259,23 +5270,23 @@ void Compile::igv_print_method_to_file(const char* phase_name, bool append) {
_debug_file_printer->update_compiled_method(C->method());
}
tty->print_cr("Method %s to %s", append ? "appended" : "printed", file_name);
_debug_file_printer->print_graph(phase_name);
_debug_file_printer->print_graph(phase_name, fr);
}
void Compile::igv_print_method_to_network(const char* phase_name) {
void Compile::igv_print_method_to_network(const char* phase_name, const frame* fr) {
ResourceMark rm;
GrowableArray<const Node*> empty_list;
igv_print_graph_to_network(phase_name, empty_list);
igv_print_graph_to_network(phase_name, empty_list, fr);
}
void Compile::igv_print_graph_to_network(const char* name, GrowableArray<const Node*>& visible_nodes) {
void Compile::igv_print_graph_to_network(const char* name, GrowableArray<const Node*>& visible_nodes, const frame* fr) {
if (_debug_network_printer == nullptr) {
_debug_network_printer = new IdealGraphPrinter(C);
} else {
_debug_network_printer->update_compiled_method(C->method());
}
tty->print_cr("Method printed over network stream to IGV");
_debug_network_printer->print(name, C->root(), visible_nodes);
_debug_network_printer->print(name, C->root(), visible_nodes, fr);
}
#endif

View File

@ -673,13 +673,13 @@ public:
void init_igv();
void dump_igv(const char* graph_name, int level = 3) {
if (should_print_igv(level)) {
_igv_printer->print_graph(graph_name);
_igv_printer->print_graph(graph_name, nullptr);
}
}
void igv_print_method_to_file(const char* phase_name = "Debug", bool append = false);
void igv_print_method_to_network(const char* phase_name = "Debug");
void igv_print_graph_to_network(const char* name, GrowableArray<const Node*>& visible_nodes);
void igv_print_method_to_file(const char* phase_name = nullptr, bool append = false, const frame* fr = nullptr);
void igv_print_method_to_network(const char* phase_name = nullptr, const frame* fr = nullptr);
void igv_print_graph_to_network(const char* name, GrowableArray<const Node*>& visible_nodes, const frame* fr);
static IdealGraphPrinter* debug_file_printer() { return _debug_file_printer; }
static IdealGraphPrinter* debug_network_printer() { return _debug_network_printer; }
#endif

View File

@ -27,8 +27,10 @@
#include "opto/idealGraphPrinter.hpp"
#include "opto/machnode.hpp"
#include "opto/parse.hpp"
#include "runtime/arguments.hpp"
#include "runtime/threadCritical.hpp"
#include "runtime/threadSMR.hpp"
#include "utilities/decoder.hpp"
#include "utilities/stringUtils.hpp"
#ifndef PRODUCT
@ -49,6 +51,13 @@ const char *IdealGraphPrinter::REMOVE_EDGE_ELEMENT = "removeEdge";
const char *IdealGraphPrinter::REMOVE_NODE_ELEMENT = "removeNode";
const char *IdealGraphPrinter::COMPILATION_ID_PROPERTY = "compilationId";
const char *IdealGraphPrinter::COMPILATION_OSR_PROPERTY = "osr";
const char *IdealGraphPrinter::COMPILATION_ARGUMENTS_PROPERTY = "arguments";
const char *IdealGraphPrinter::COMPILATION_MACHINE_PROPERTY = "machine";
const char *IdealGraphPrinter::COMPILATION_CPU_FEATURES_PROPERTY = "cpuFeatures";
const char *IdealGraphPrinter::COMPILATION_VM_VERSION_PROPERTY = "vm";
const char *IdealGraphPrinter::COMPILATION_DATE_TIME_PROPERTY = "dateTime";
const char *IdealGraphPrinter::COMPILATION_PROCESS_ID_PROPERTY = "processId";
const char *IdealGraphPrinter::COMPILATION_THREAD_ID_PROPERTY = "threadId";
const char *IdealGraphPrinter::METHOD_NAME_PROPERTY = "name";
const char *IdealGraphPrinter::METHOD_IS_PUBLIC_PROPERTY = "public";
const char *IdealGraphPrinter::METHOD_IS_STATIC_PROPERTY = "static";
@ -336,6 +345,42 @@ void IdealGraphPrinter::begin_method() {
print_prop(COMPILATION_ID_PROPERTY, C->compile_id());
stringStream args;
Arguments::print_jvm_args_on(&args);
print_prop(COMPILATION_ARGUMENTS_PROPERTY, args.freeze());
stringStream machine;
buffer[0] = 0;
os::print_summary_info(&machine, buffer, sizeof(buffer) - 1);
print_prop(COMPILATION_MACHINE_PROPERTY, machine.freeze());
print_prop(COMPILATION_CPU_FEATURES_PROPERTY, VM_Version::features_string());
stringStream version;
buffer[0] = 0;
JDK_Version::current().to_string(buffer, sizeof(buffer) - 1);
const char* runtime_name = JDK_Version::runtime_name() != nullptr ?
JDK_Version::runtime_name() : "";
const char* runtime_version = JDK_Version::runtime_version() != nullptr ?
JDK_Version::runtime_version() : "";
const char* vendor_version = JDK_Version::runtime_vendor_version() != nullptr ?
JDK_Version::runtime_vendor_version() : "";
const char* jdk_debug_level = VM_Version::printable_jdk_debug_level() != nullptr ?
VM_Version::printable_jdk_debug_level() : "";
version.print_cr("%s%s%s (%s) (%sbuild %s)", runtime_name,
(*vendor_version != '\0') ? " " : "", vendor_version,
buffer, jdk_debug_level, runtime_version);
print_prop(COMPILATION_VM_VERSION_PROPERTY, version.freeze());
stringStream time;
buffer[0] = 0;
os::print_date_and_time(&time, buffer, sizeof(buffer) - 1);
print_prop(COMPILATION_DATE_TIME_PROPERTY, time.freeze());
print_prop(COMPILATION_PROCESS_ID_PROPERTY, os::current_process_id());
print_prop(COMPILATION_THREAD_ID_PROPERTY, os::current_thread_id());
tail(PROPERTIES_ELEMENT);
_should_send_method = true;
@ -862,24 +907,91 @@ void IdealGraphPrinter::walk_nodes(Node* start, bool edges) {
}
}
void IdealGraphPrinter::print_graph(const char* name) {
// Whether the stack walk should skip the given frame when producing a C2 stack
// trace. We consider IGV- and debugger-specific frames uninteresting.
static bool should_skip_frame(const char* name) {
return strstr(name, "IdealGraphPrinter") != nullptr ||
strstr(name, "Compile::print_method") != nullptr ||
strstr(name, "Compile::igv_print_graph") != nullptr ||
strstr(name, "PrintBFS") != nullptr ||
strstr(name, "Node::dump_bfs") != nullptr;
}
// Whether the stack walk should be considered done when visiting a certain
// frame. The purpose of walking the stack is producing a C2 trace, so we
// consider all frames below (and including) C2Compiler::compile_method(..)
// uninteresting.
static bool should_end_stack_walk(const char* name) {
return strstr(name, "C2Compiler::compile_method") != nullptr;
}
void IdealGraphPrinter::print_stack(const frame* initial_frame, outputStream* graph_name) {
char buf[O_BUFLEN];
frame fr = initial_frame == nullptr ? os::current_frame() : *initial_frame;
int frame = 0;
for (int count = 0; count < StackPrintLimit && fr.pc() != nullptr; count++) {
int offset;
buf[0] = '\0';
bool found = os::dll_address_to_function_name(fr.pc(), buf, sizeof(buf), &offset);
if (!found || should_end_stack_walk(buf)) {
break;
}
if (!should_skip_frame(buf)) {
stringStream frame_loc;
frame_loc.print("%s", buf);
buf[0] = '\0';
int line_no;
if (Decoder::get_source_info(fr.pc(), buf, sizeof(buf), &line_no, count != 1)) {
frame_loc.print(" (%s:%d)", buf, line_no);
if (graph_name != nullptr) {
// Extract a debug graph name and return.
graph_name->print("%s:%d", buf, line_no);
return;
}
}
if (graph_name == nullptr) {
// Print frame as IGV property and continue to the next frame.
stringStream frame_number_str;
frame_number_str.print("frame %d:", frame);
print_prop(frame_number_str.freeze(), frame_loc.freeze());
frame++;
}
}
fr = frame::next_frame(fr, Thread::current_or_null());
}
}
void IdealGraphPrinter::print_graph(const char* name, const frame* fr) {
ResourceMark rm;
GrowableArray<const Node*> empty_list;
print(name, (Node*) C->root(), empty_list);
print(name, (Node*) C->root(), empty_list, fr);
}
// Print current ideal graph
void IdealGraphPrinter::print(const char* name, Node* node, GrowableArray<const Node*>& visible_nodes) {
void IdealGraphPrinter::print(const char* name, Node* node, GrowableArray<const Node*>& visible_nodes, const frame* fr) {
if (!_current_method || !_should_send_method || node == nullptr) return;
if (name == nullptr) {
stringStream graph_name;
print_stack(fr, &graph_name);
name = graph_name.freeze();
if (strlen(name) == 0) {
name = "Debug";
}
}
// Warning, unsafe cast?
_chaitin = (PhaseChaitin *)C->regalloc();
begin_head(GRAPH_ELEMENT);
print_attr(GRAPH_NAME_PROPERTY, (const char *)name);
print_attr(GRAPH_NAME_PROPERTY, name);
end_head();
head(PROPERTIES_ELEMENT);
print_stack(fr, nullptr);
tail(PROPERTIES_ELEMENT);
head(NODES_ELEMENT);
if (C->cfg() != nullptr) {
// Compute the maximum estimated frequency in the current graph.

View File

@ -67,6 +67,13 @@ class IdealGraphPrinter : public CHeapObj<mtCompiler> {
static const char *ALL_PROPERTY;
static const char *COMPILATION_ID_PROPERTY;
static const char *COMPILATION_OSR_PROPERTY;
static const char *COMPILATION_ARGUMENTS_PROPERTY;
static const char *COMPILATION_MACHINE_PROPERTY;
static const char *COMPILATION_CPU_FEATURES_PROPERTY;
static const char *COMPILATION_VM_VERSION_PROPERTY;
static const char *COMPILATION_DATE_TIME_PROPERTY;
static const char *COMPILATION_PROCESS_ID_PROPERTY;
static const char *COMPILATION_THREAD_ID_PROPERTY;
static const char *METHOD_NAME_PROPERTY;
static const char *BLOCK_NAME_PROPERTY;
static const char *BLOCK_DOMINATOR_PROPERTY;
@ -110,6 +117,10 @@ class IdealGraphPrinter : public CHeapObj<mtCompiler> {
double _max_freq;
bool _append;
// Walk the native stack and print relevant C2 frames as IGV properties (if
// graph_name == nullptr) or the graph name based on the highest C2 frame (if
// graph_name != nullptr).
void print_stack(const frame* initial_frame, outputStream* graph_name);
void print_method(ciMethod* method, int bci, InlineTree* tree);
void print_inline_tree(InlineTree* tree);
void visit_node(Node* n, bool edges);
@ -149,8 +160,8 @@ class IdealGraphPrinter : public CHeapObj<mtCompiler> {
void print_inlining();
void begin_method();
void end_method();
void print_graph(const char* name);
void print(const char* name, Node* root, GrowableArray<const Node*>& hidden_nodes);
void print_graph(const char* name, const frame* fr = nullptr);
void print(const char* name, Node* root, GrowableArray<const Node*>& hidden_nodes, const frame* fr = nullptr);
void set_compile(Compile* compile) {C = compile; }
void update_compiled_method(ciMethod* current_method);
};

View File

@ -1774,8 +1774,8 @@ Node* Node::find(const int idx, bool only_ctrl) {
class PrintBFS {
public:
PrintBFS(const Node* start, const int max_distance, const Node* target, const char* options, outputStream* st)
: _start(start), _max_distance(max_distance), _target(target), _options(options), _output(st),
PrintBFS(const Node* start, const int max_distance, const Node* target, const char* options, outputStream* st, const frame* fr)
: _start(start), _max_distance(max_distance), _target(target), _options(options), _output(st), _frame(fr),
_dcc(this), _info_uid(cmpkey, hashkey) {}
void run();
@ -1796,6 +1796,7 @@ private:
const Node* _target;
const char* _options;
outputStream* _output;
const frame* _frame;
// options
bool _traverse_inputs = false;
@ -2057,7 +2058,7 @@ void PrintBFS::print() {
if (_print_igv) {
Compile* C = Compile::current();
C->init_igv();
C->igv_print_graph_to_network("PrintBFS", _print_list);
C->igv_print_graph_to_network(nullptr, _print_list, _frame);
}
} else {
_output->print_cr("No nodes to print.");
@ -2102,6 +2103,8 @@ void PrintBFS::print_options_help(bool print_examples) {
_output->print_cr(" B: print scheduling blocks (if available)");
_output->print_cr(" $: dump only, no header, no other columns");
_output->print_cr(" !: show nodes on IGV (sent over network stream)");
_output->print_cr(" (use preferably with dump_bfs(int, Node*, char*, void*, void*, void*)");
_output->print_cr(" to produce a C2 stack trace along with the graph dump, see examples below)");
_output->print_cr("");
_output->print_cr("recursively follow edges to nodes with permitted visit types,");
_output->print_cr("on the boundary additionally display nodes allowed in boundary types");
@ -2151,6 +2154,9 @@ void PrintBFS::print_options_help(bool print_examples) {
_output->print_cr(" find all paths (A) between two nodes of length at most 8");
_output->print_cr(" find_node(741)->dump_bfs(7, find_node(741), \"c+A\")");
_output->print_cr(" find all control loops for this node");
_output->print_cr(" find_node(741)->dump_bfs(7, find_node(741), \"c+A!\", $sp, $fp, $pc)");
_output->print_cr(" same as above, but printing the resulting subgraph");
_output->print_cr(" along with a C2 stack trace on IGV");
}
}
@ -2409,8 +2415,8 @@ void Node::dump_bfs(const int max_distance, Node* target, const char* options) c
}
// Used to dump to stream.
void Node::dump_bfs(const int max_distance, Node* target, const char* options, outputStream* st) const {
PrintBFS bfs(this, max_distance, target, options, st);
void Node::dump_bfs(const int max_distance, Node* target, const char* options, outputStream* st, const frame* fr) const {
PrintBFS bfs(this, max_distance, target, options, st, fr);
bfs.run();
}
@ -2419,6 +2425,13 @@ void Node::dump_bfs(const int max_distance) const {
dump_bfs(max_distance, nullptr, nullptr);
}
// Call this from debugger, with stack handling register arguments for IGV dumps.
// Example: p find_node(741)->dump_bfs(7, find_node(741), "c+A!", $sp, $fp, $pc).
void Node::dump_bfs(const int max_distance, Node* target, const char* options, void* sp, void* fp, void* pc) const {
frame fr(sp, fp, pc);
dump_bfs(max_distance, target, options, tty, &fr);
}
// -----------------------------dump_idx---------------------------------------
void Node::dump_idx(bool align, outputStream* st, DumpConfig* dc) const {
if (dc != nullptr) {

View File

@ -1298,9 +1298,10 @@ public:
public:
Node* find(int idx, bool only_ctrl = false); // Search the graph for the given idx.
Node* find_ctrl(int idx); // Search control ancestors for the given idx.
void dump_bfs(const int max_distance, Node* target, const char* options, outputStream* st) const;
void dump_bfs(const int max_distance, Node* target, const char* options, outputStream* st, const frame* fr = nullptr) const;
void dump_bfs(const int max_distance, Node* target, const char* options) const; // directly to tty
void dump_bfs(const int max_distance) const; // dump_bfs(max_distance, nullptr, nullptr)
void dump_bfs(const int max_distance, Node* target, const char* options, void* sp, void* fp, void* pc) const;
class DumpConfig {
public:
// overridden to implement coloring of node idx

View File

@ -22,6 +22,8 @@ for building and running IGV.
# Usage
## Regular JVM Execution
The JVM support is controlled by the flag `-XX:PrintIdealGraphLevel=N`, where
Ideal graphs are dumped at the following points:
@ -48,6 +50,59 @@ Alternatively the output can be sent to a file using
with unique names being generated by adding a number onto the provided file
name.
## Dumping Graphs From a Debugger
The JVM provides some entry functions to dump graphs from a debugger such as
`gdb` or `rr`, see the different variants of `igv_print` and `igv_append` in
[`compile.cpp`](/src/hotspot/share/opto/compile.cpp). In combination with the
IGV network interface, these functions enable a powerful interactive workflow
where the user can simultaneously step through C2's code and visualize the
evolving Ideal graph. Note that, to produce and print meaningful C2 stack
traces, these functions take the stack pointer, frame pointer, and program
counter registers as arguments. These are usually `$sp`, `$fp`, and `$pc`:
```
(gdb) p igv_print(true, $sp, $fp, $pc)
Method printed over network stream to IGV
```
but might be given different names on different platforms, see the output of
`p help()` for more information. A tip to further simplify the workflow in
`gdb` or `rr` is to create a user-defined command such as e.g.:
```
define igv
p igv_print(true, $sp, $fp, $pc)
end
```
On `lldb`, it might be necessary to explicitly cast the registers to `void*` to
ensure their values are correctly passed as arguments:
```
(lldb) p igv_print(true, (void*)$sp, (void*)$fp, (void*)$pc)
```
Another way to dump graphs interactively is through the `Node::dump_bfs`
functionality with the option `!` (run `p find_node(0)->dump_bfs(0,0,"H")` to
see the complete list of options). One of the versions of this function also
takes the three stack management registers to produce a C2 stack trace:
```
(gdb) p find_node(3)->dump_bfs(0, 0, "!", $sp, $fp, $pc)
(...)
Method printed over network stream to IGV
```
Again, user-defined debugger commands can be created for simplicity. For
example, to dump the current graph with a given node highlighted:
```
define igv_node
p find_node($arg0)->dump_bfs(0, 0, "!", $sp, $fp, $pc)
end
```
## Defining Custom Filters
IGV has a powerful filter mechanism with which nodes and blocks can be colored,