From def907ab89f3e5593aef17dcc61807e2836d41ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Casta=C3=B1eda=20Lozano?= Date: Tue, 6 May 2025 08:16:09 +0000 Subject: [PATCH] 8354520: IGV: dump contextual information Reviewed-by: epeter, dfenacci --- src/hotspot/share/opto/compile.cpp | 55 +++++---- src/hotspot/share/opto/compile.hpp | 8 +- src/hotspot/share/opto/idealGraphPrinter.cpp | 120 ++++++++++++++++++- src/hotspot/share/opto/idealGraphPrinter.hpp | 15 ++- src/hotspot/share/opto/node.cpp | 23 +++- src/hotspot/share/opto/node.hpp | 3 +- src/utils/IdealGraphVisualizer/README.md | 55 +++++++++ 7 files changed, 241 insertions(+), 38 deletions(-) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index effc7e21354..10846a32626 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -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 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& visible_nodes) { +void Compile::igv_print_graph_to_network(const char* name, GrowableArray& 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 diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 33b9187e262..6d43948964e 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -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& 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& visible_nodes, const frame* fr); static IdealGraphPrinter* debug_file_printer() { return _debug_file_printer; } static IdealGraphPrinter* debug_network_printer() { return _debug_network_printer; } #endif diff --git a/src/hotspot/share/opto/idealGraphPrinter.cpp b/src/hotspot/share/opto/idealGraphPrinter.cpp index 9f17e350059..f532b7e5d6e 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.cpp +++ b/src/hotspot/share/opto/idealGraphPrinter.cpp @@ -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 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& visible_nodes) { +void IdealGraphPrinter::print(const char* name, Node* node, GrowableArray& 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. diff --git a/src/hotspot/share/opto/idealGraphPrinter.hpp b/src/hotspot/share/opto/idealGraphPrinter.hpp index a8e0e3462e7..69ba2841506 100644 --- a/src/hotspot/share/opto/idealGraphPrinter.hpp +++ b/src/hotspot/share/opto/idealGraphPrinter.hpp @@ -67,6 +67,13 @@ class IdealGraphPrinter : public CHeapObj { 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 { 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 { void print_inlining(); void begin_method(); void end_method(); - void print_graph(const char* name); - void print(const char* name, Node* root, GrowableArray& hidden_nodes); + void print_graph(const char* name, const frame* fr = nullptr); + void print(const char* name, Node* root, GrowableArray& hidden_nodes, const frame* fr = nullptr); void set_compile(Compile* compile) {C = compile; } void update_compiled_method(ciMethod* current_method); }; diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index a635abebec1..3875c6ce325 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -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) { diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index e1a4ee22661..843baf48cf8 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -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 diff --git a/src/utils/IdealGraphVisualizer/README.md b/src/utils/IdealGraphVisualizer/README.md index 904c7b9eaf6..195878a5b99 100644 --- a/src/utils/IdealGraphVisualizer/README.md +++ b/src/utils/IdealGraphVisualizer/README.md @@ -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,