8356761: IGV: dump escape analysis information

Reviewed-by: rcastanedalo, chagedorn
This commit is contained in:
Anton Seoane Ampudia 2025-11-14 07:25:44 +00:00 committed by Roberto Castañeda Lozano
parent 1baf5164d6
commit 0829c6acde
10 changed files with 204 additions and 5 deletions

View File

@ -114,11 +114,13 @@ void ConnectionGraph::do_analysis(Compile *C, PhaseIterGVN *igvn) {
invocation = C->congraph()->_invocation + 1;
}
ConnectionGraph* congraph = new(C->comp_arena()) ConnectionGraph(C, igvn, invocation);
NOT_PRODUCT(if (C->should_print_igv(/* Any level */ 1)) C->igv_printer()->set_congraph(congraph);)
// Perform escape analysis
if (congraph->compute_escape()) {
// There are non escaping objects.
C->set_congraph(congraph);
}
NOT_PRODUCT(if (C->should_print_igv(/* Any level */ 1)) C->igv_printer()->set_congraph(nullptr);)
// Cleanup.
if (oop_null->outcnt() == 0) {
igvn->hash_delete(oop_null);
@ -126,6 +128,8 @@ void ConnectionGraph::do_analysis(Compile *C, PhaseIterGVN *igvn) {
if (noop_null->outcnt() == 0) {
igvn->hash_delete(noop_null);
}
C->print_method(PHASE_AFTER_EA, 2);
}
bool ConnectionGraph::compute_escape() {
@ -281,6 +285,8 @@ bool ConnectionGraph::compute_escape() {
return false;
}
_compile->print_method(PHASE_EA_AFTER_INITIAL_CONGRAPH, 4);
// 2. Finish Graph construction by propagating references to all
// java objects through graph.
if (!complete_connection_graph(ptnodes_worklist, non_escaped_allocs_worklist,
@ -291,6 +297,8 @@ bool ConnectionGraph::compute_escape() {
return false;
}
_compile->print_method(PHASE_EA_AFTER_COMPLETE_CONGRAPH, 4);
// 3. Adjust scalar_replaceable state of nonescaping objects and push
// scalar replaceable allocations on alloc_worklist for processing
// in split_unique_types().
@ -312,6 +320,7 @@ bool ConnectionGraph::compute_escape() {
found_nsr_alloc = true;
}
}
_compile->print_method(PHASE_EA_ADJUST_SCALAR_REPLACEABLE_ITER, 6, n);
}
// Propagate NSR (Not Scalar Replaceable) state.
@ -350,6 +359,7 @@ bool ConnectionGraph::compute_escape() {
_collecting = false;
_compile->print_method(PHASE_EA_AFTER_PROPAGATE_NSR, 4);
} // TracePhase t3("connectionGraph")
// 4. Optimize ideal graph based on EA information.
@ -387,6 +397,8 @@ bool ConnectionGraph::compute_escape() {
}
#endif
_compile->print_method(PHASE_EA_AFTER_GRAPH_OPTIMIZATION, 4);
// 5. Separate memory graph for scalar replaceable allcations.
bool has_scalar_replaceable_candidates = (alloc_worklist.length() > 0);
if (has_scalar_replaceable_candidates && EliminateAllocations) {
@ -398,7 +410,6 @@ bool ConnectionGraph::compute_escape() {
NOT_PRODUCT(escape_state_statistics(java_objects_worklist);)
return false;
}
C->print_method(PHASE_AFTER_EA, 2);
#ifdef ASSERT
} else if (Verbose && (PrintEscapeAnalysis || PrintEliminateAllocations)) {
@ -413,6 +424,8 @@ bool ConnectionGraph::compute_escape() {
#endif
}
_compile->print_method(PHASE_EA_AFTER_SPLIT_UNIQUE_TYPES, 4);
// 6. Reduce allocation merges used as debug information. This is done after
// split_unique_types because the methods used to create SafePointScalarObject
// need to traverse the memory graph to find values for object fields. We also
@ -454,6 +467,8 @@ bool ConnectionGraph::compute_escape() {
}
}
_compile->print_method(PHASE_EA_AFTER_REDUCE_PHI_ON_SAFEPOINTS, 4);
NOT_PRODUCT(escape_state_statistics(java_objects_worklist);)
return has_non_escaping_obj;
}
@ -1302,11 +1317,14 @@ void ConnectionGraph::reduce_phi(PhiNode* ophi, GrowableArray<Node*> &alloc_work
}
}
_compile->print_method(PHASE_EA_BEFORE_PHI_REDUCTION, 5, ophi);
// CastPPs need to be processed before Cmps because during the process of
// splitting CastPPs we make reference to the inputs of the Cmp that is used
// by the If controlling the CastPP.
for (uint i = 0; i < castpps.size(); i++) {
reduce_phi_on_castpp_field_load(castpps.at(i), alloc_worklist);
_compile->print_method(PHASE_EA_AFTER_PHI_CASTPP_REDUCTION, 6, castpps.at(i));
}
for (uint i = 0; i < others.size(); i++) {
@ -1314,8 +1332,10 @@ void ConnectionGraph::reduce_phi(PhiNode* ophi, GrowableArray<Node*> &alloc_work
if (use->is_AddP()) {
reduce_phi_on_field_access(use, alloc_worklist);
_compile->print_method(PHASE_EA_AFTER_PHI_ADDP_REDUCTION, 6, use);
} else if(use->is_Cmp()) {
reduce_phi_on_cmp(use);
_compile->print_method(PHASE_EA_AFTER_PHI_CMP_REDUCTION, 6, use);
}
}
@ -2417,6 +2437,7 @@ bool ConnectionGraph::complete_connection_graph(
timeout = true;
break;
}
_compile->print_method(PHASE_EA_COMPLETE_CONNECTION_GRAPH_ITER, 5);
}
if ((iterations < GRAPH_BUILD_ITER_LIMIT) && !timeout) {
time.start();
@ -2490,7 +2511,8 @@ bool ConnectionGraph::complete_connection_graph(
// Propagate GlobalEscape and ArgEscape escape states to all nodes
// and check that we still have non-escaping java objects.
bool ConnectionGraph::find_non_escaped_objects(GrowableArray<PointsToNode*>& ptnodes_worklist,
GrowableArray<JavaObjectNode*>& non_escaped_allocs_worklist) {
GrowableArray<JavaObjectNode*>& non_escaped_allocs_worklist,
bool print_method) {
GrowableArray<PointsToNode*> escape_worklist;
// First, put all nodes with GlobalEscape and ArgEscape states on worklist.
int ptnodes_length = ptnodes_worklist.length();
@ -2550,6 +2572,9 @@ bool ConnectionGraph::find_non_escaped_objects(GrowableArray<PointsToNode*>& ptn
escape_worklist.push(e);
}
}
if (print_method) {
_compile->print_method(PHASE_EA_CONNECTION_GRAPH_PROPAGATE_ITER, 6, e->ideal_node());
}
}
}
// Remove escaped objects from non_escaped list.
@ -3137,6 +3162,7 @@ void ConnectionGraph::find_scalar_replaceable_allocs(GrowableArray<JavaObjectNod
break;
}
}
_compile->print_method(PHASE_EA_PROPAGATE_NSR_ITER, 5, jobj->ideal_node());
}
}
}
@ -3159,7 +3185,7 @@ void ConnectionGraph::verify_connection_graph(
assert(new_edges == 0, "graph was not complete");
// Verify that escape state is final.
int length = non_escaped_allocs_worklist.length();
find_non_escaped_objects(ptnodes_worklist, non_escaped_allocs_worklist);
find_non_escaped_objects(ptnodes_worklist, non_escaped_allocs_worklist, /*print_method=*/ false);
assert((non_escaped_length == non_escaped_allocs_worklist.length()) &&
(non_escaped_length == length) &&
(_worklist.length() == 0), "escape state was not final");
@ -4720,6 +4746,8 @@ void ConnectionGraph::split_unique_types(GrowableArray<Node *> &alloc_worklist,
// New alias types were created in split_AddP().
uint new_index_end = (uint) _compile->num_alias_types();
_compile->print_method(PHASE_EA_AFTER_SPLIT_UNIQUE_TYPES_1, 5);
// Phase 2: Process MemNode's from memnode_worklist. compute new address type and
// compute new values for Memory inputs (the Memory inputs are not
// actually updated until phase 4.)
@ -4920,6 +4948,8 @@ void ConnectionGraph::split_unique_types(GrowableArray<Node *> &alloc_worklist,
record_for_optimizer(nmm);
}
_compile->print_method(PHASE_EA_AFTER_SPLIT_UNIQUE_TYPES_3, 5);
// Phase 4: Update the inputs of non-instance memory Phis and
// the Memory input of memnodes
// First update the inputs of any non-instance Phi's from
@ -4988,6 +5018,7 @@ void ConnectionGraph::split_unique_types(GrowableArray<Node *> &alloc_worklist,
assert(old_cnt == old_mem->outcnt(), "old mem could be lost");
}
#endif
_compile->print_method(PHASE_EA_AFTER_SPLIT_UNIQUE_TYPES_4, 5);
}
#ifndef PRODUCT
@ -5010,6 +5041,10 @@ static const char *esc_names[] = {
"GlobalEscape"
};
const char* PointsToNode::esc_name() const {
return esc_names[(int)escape_state()];
}
void PointsToNode::dump_header(bool print_state, outputStream* out) const {
NodeType nt = node_type();
out->print("%s(%d) ", node_type_names[(int) nt], _pidx);

View File

@ -26,6 +26,7 @@
#define SHARE_OPTO_ESCAPE_HPP
#include "opto/addnode.hpp"
#include "opto/idealGraphPrinter.hpp"
#include "opto/node.hpp"
#include "utilities/growableArray.hpp"
@ -235,6 +236,7 @@ public:
NodeType node_type() const { return (NodeType)_type;}
void dump(bool print_state=true, outputStream* out=tty, bool newline=true) const;
void dump_header(bool print_state=true, outputStream* out=tty) const;
const char* esc_name() const;
#endif
};
@ -321,6 +323,7 @@ public:
class ConnectionGraph: public ArenaObj {
friend class PointsToNode; // to access _compile
friend class FieldNode;
friend class IdealGraphPrinter;
private:
GrowableArray<PointsToNode*> _nodes; // Map from ideal nodes to
// ConnectionGraph nodes.
@ -467,7 +470,8 @@ private:
// Propagate GlobalEscape and ArgEscape escape states to all nodes
// and check that we still have non-escaping java objects.
bool find_non_escaped_objects(GrowableArray<PointsToNode*>& ptnodes_worklist,
GrowableArray<JavaObjectNode*>& non_escaped_worklist);
GrowableArray<JavaObjectNode*>& non_escaped_worklist,
bool print_method = true);
// Adjust scalar_replaceable state after Connection Graph is built.
void adjust_scalar_replaceable_state(JavaObjectNode* jobj, Unique_Node_List &reducible_merges);

View File

@ -24,6 +24,7 @@
#include "memory/resourceArea.hpp"
#include "opto/chaitin.hpp"
#include "opto/escape.hpp"
#include "opto/idealGraphPrinter.hpp"
#include "opto/machnode.hpp"
#include "opto/parse.hpp"
@ -161,6 +162,7 @@ void IdealGraphPrinter::init(const char* file_name, bool use_multiple_files, boo
_current_method = nullptr;
_network_stream = nullptr;
_append = append;
_congraph = nullptr;
_parse = nullptr;
if (file_name != nullptr) {
@ -637,6 +639,29 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) {
print_prop("is_block_start", "true");
}
// Dump escape analysis state for relevant nodes.
if (node->is_Allocate()) {
AllocateNode* alloc = node->as_Allocate();
if (alloc->_is_scalar_replaceable) {
print_prop("is_scalar_replaceable", "true");
}
if (alloc->_is_non_escaping) {
print_prop("is_non_escaping", "true");
}
if (alloc->does_not_escape_thread()) {
print_prop("does_not_escape_thread", "true");
}
}
if (node->is_SafePoint() && node->as_SafePoint()->has_ea_local_in_scope()) {
print_prop("has_ea_local_in_scope", "true");
}
if (node->is_CallJava() && node->as_CallJava()->arg_escape()) {
print_prop("arg_escape", "true");
}
if (node->is_Initialize() && node->as_Initialize()->does_not_escape()) {
print_prop("does_not_escape", "true");
}
const char *short_name = "short_name";
if (strcmp(node->Name(), "Parm") == 0 && node->as_Proj()->_con >= TypeFunc::Parms) {
int index = node->as_Proj()->_con - TypeFunc::Parms;
@ -731,6 +756,19 @@ void IdealGraphPrinter::visit_node(Node* n, bool edges) {
print_prop("lrg", lrg_id);
}
if (_congraph != nullptr && node->_idx < _congraph->nodes_size()) {
PointsToNode* ptn = _congraph->ptnode_adr(node->_idx);
if (ptn != nullptr) {
stringStream node_head;
ptn->dump_header(false, &node_head);
print_prop("ea_node", node_head.freeze());
print_prop("escape_state", ptn->esc_name());
if (ptn->scalar_replaceable()) {
print_prop("scalar_replaceable", "true");
}
}
}
if (node->is_MachSafePoint()) {
const OopMap* oopmap = node->as_MachSafePoint()->oop_map();
if (oopmap != nullptr) {

View File

@ -42,6 +42,7 @@ class Node;
class InlineTree;
class ciMethod;
class JVMState;
class ConnectionGraph;
class Parse;
class IdealGraphPrinter : public CHeapObj<mtCompiler> {
@ -116,6 +117,7 @@ class IdealGraphPrinter : public CHeapObj<mtCompiler> {
Compile *C;
double _max_freq;
bool _append;
ConnectionGraph* _congraph;
const Parse* _parse;
// Walk the native stack and print relevant C2 frames as IGV properties (if
@ -165,6 +167,7 @@ class IdealGraphPrinter : public CHeapObj<mtCompiler> {
void end_method();
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_congraph(ConnectionGraph* congraph) { _congraph = congraph; }
void set_compile(Compile* compile) {C = compile; }
void update_compiled_method(ciMethod* current_method);
};

View File

@ -50,6 +50,23 @@
flags(ITER_GVN_AFTER_VECTOR, "Iter GVN after Vector Box Elimination") \
flags(BEFORE_LOOP_OPTS, "Before Loop Optimizations") \
flags(PHASEIDEAL_BEFORE_EA, "PhaseIdealLoop before EA") \
flags(EA_AFTER_INITIAL_CONGRAPH, "EA: 1. Intial Connection Graph") \
flags(EA_CONNECTION_GRAPH_PROPAGATE_ITER, "EA: 2. Connection Graph Propagate Iter") \
flags(EA_COMPLETE_CONNECTION_GRAPH_ITER, "EA: 2. Complete Connection Graph Iter") \
flags(EA_AFTER_COMPLETE_CONGRAPH, "EA: 2. Complete Connection Graph") \
flags(EA_ADJUST_SCALAR_REPLACEABLE_ITER, "EA: 3. Adjust scalar_replaceable State Iter") \
flags(EA_PROPAGATE_NSR_ITER, "EA: 3. Propagate NSR Iter") \
flags(EA_AFTER_PROPAGATE_NSR, "EA: 3. Propagate NSR") \
flags(EA_AFTER_GRAPH_OPTIMIZATION, "EA: 4. After Graph Optimization") \
flags(EA_AFTER_SPLIT_UNIQUE_TYPES_1, "EA: 5. After split_unique_types Phase 1") \
flags(EA_AFTER_SPLIT_UNIQUE_TYPES_3, "EA: 5. After split_unique_types Phase 3") \
flags(EA_AFTER_SPLIT_UNIQUE_TYPES_4, "EA: 5. After split_unique_types Phase 4") \
flags(EA_AFTER_SPLIT_UNIQUE_TYPES, "EA: 5. After split_unique_types") \
flags(EA_AFTER_REDUCE_PHI_ON_SAFEPOINTS, "EA: 6. After reduce_phi_on_safepoints") \
flags(EA_BEFORE_PHI_REDUCTION, "EA: 5. Before Phi Reduction") \
flags(EA_AFTER_PHI_CASTPP_REDUCTION, "EA: 5. Phi -> CastPP Reduction") \
flags(EA_AFTER_PHI_ADDP_REDUCTION, "EA: 5. Phi -> AddP Reduction") \
flags(EA_AFTER_PHI_CMP_REDUCTION, "EA: 5. Phi -> Cmp Reduction") \
flags(AFTER_EA, "After Escape Analysis") \
flags(ITER_GVN_AFTER_EA, "Iter GVN after EA") \
flags(BEFORE_BEAUTIFY_LOOPS, "Before Beautify Loops") \

View File

@ -0,0 +1,51 @@
// Color those nodes present in the escape analysis connection graph
// to indicate the result of scape analysis.
// This filter is relevant between the first EA phase and "After Macro
// Expansion".
var bestColor = java.awt.Color.decode("#6aa84f"); // Green.
var betterColor = java.awt.Color.decode("#f1c232"); // Yellow.
var worseColor = java.awt.Color.decode("#e69138"); // Orange.
var worstColor = java.awt.Color.decode("#cc0000"); // Red.
// Apply first colors based on persistent node attributes
// Object does not escape compilation unit and is scalar replaceable.
colorize(and([hasProperty("is_non_escaping"),
hasProperty("is_scalar_replaceable")]),
bestColor);
// Object does not escape compilation unit but is not scalar replaceable,
// due to of scalar replacement limitations. We can at least elide locks.
colorize(and([hasProperty("is_non_escaping"),
not(hasProperty("is_scalar_replaceable"))]),
betterColor);
// Object may escape compilation unit but does not escape thread.
// We can at least elide locks.
colorize(and([hasProperty("does_not_escape_thread"),
not(hasProperty("is_non_escaping"))]),
worseColor);
// Object may escape compilation unit and thread. Nothing to do.
colorize(and([matches("name", "Allocate"),
not(hasProperty("is_non_escaping")),
not(hasProperty("does_not_escape_thread"))]),
worstColor);
// Apply colors again based on connection graph-derived attributes
colorize(and([matches("escape_state", "NoEscape"),
hasProperty("scalar_replaceable")]),
bestColor);
colorize(and([matches("escape_state", "NoEscape"),
not(hasProperty("scalar_replaceable"))]),
betterColor);
colorize(and([matches("escape_state", "ArgEscape"),
not(hasProperty("scalar_replaceable"))]),
worseColor);
colorize(matches("escape_state", "GlobalEscape"),
worstColor);

View File

@ -0,0 +1,6 @@
// This filter shows only the nodes that are present in the escape analysis
// connection graph. This can be used to approximate the connection graph inside
// IGV.
// This filter is only relevant during the escape analysis phases.
remove(not(hasProperty("ea_node")));

View File

@ -0,0 +1,16 @@
// This filter appends escape analysis connection graph node information to the
// (possibly already existing) extra-label line.
// This is only carried out for those nodes that are relevant to escape
// analysis (and therefore represented in the connection graph).
// Merge a possibly existing extra label with the escape analysis node type into a
// new, single extra label.
function mergeAndAppendTypeInfo(extra_label, ea_node) {
new_extra_label = extra_label == null ? "" : (extra_label + " ");
return new_extra_label + ea_node;
}
editProperty(hasProperty("ea_node"),
["extra_label", "ea_node"],
"extra_label",
function(propertyValues) {return mergeAndAppendTypeInfo(propertyValues[0], propertyValues[1]);});

View File

@ -85,9 +85,21 @@
<attr name="enabled" boolvalue="false"/>
<attr name="after" stringvalue="Hide uncommon trap blocks"/>
</file>
<file name="Show connection graph info.js" url="filters/showConnectionInfo.filter">
<attr name="enabled" boolvalue="false"/>
<attr name="after" stringvalue="Hide exception blocks"/>
</file>
<file name="Show connection graph nodes only.js" url="filters/showConnectionGraphNodesOnly.filter">
<attr name="enabled" boolvalue="false"/>
<attr name="after" stringvalue="Show connection graph info"/>
</file>
<file name="Color by escape analysis state.js" url="filters/colorEscapeAnalysis.filter">
<attr name="enabled" boolvalue="false"/>
<attr name="after" stringvalue="Show connection graph nodes only"/>
</file>
<file name="Color live ranges by allocation.js" url="filters/colorLiveRanges.filter">
<attr name="enabled" boolvalue="true"/>
<attr name="after" stringvalue="Hide exception blocks"/>
<attr name="after" stringvalue="Color by escape analysis state"/>
</file>
<file name="Show liveness information.js" url="filters/showLiveness.filter">
<attr name="enabled" boolvalue="false"/>

View File

@ -60,6 +60,23 @@ public enum CompilePhase {
ITER_GVN_AFTER_VECTOR( "Iter GVN after Vector Box Elimination"),
BEFORE_LOOP_OPTS( "Before Loop Optimizations"),
PHASEIDEAL_BEFORE_EA( "PhaseIdealLoop before EA"),
EA_AFTER_INITIAL_CONGRAPH( "EA: 1. Intial Connection Graph"),
EA_CONNECTION_GRAPH_PROPAGATE_ITER("EA: 2. Connection Graph Propagate Iter"),
EA_COMPLETE_CONNECTION_GRAPH_ITER( "EA: 2. Complete Connection Graph Iter"),
EA_AFTER_COMPLETE_CONGRAPH( "EA: 2. Complete Connection Graph"),
EA_ADJUST_SCALAR_REPLACEABLE_ITER( "EA: 3. Adjust scalar_replaceable State Iter"),
EA_PROPAGATE_NSR_ITER( "EA: 3. Propagate NSR Iter"),
EA_AFTER_PROPAGATE_NSR( "EA: 3. Propagate NSR"),
EA_AFTER_GRAPH_OPTIMIZATION( "EA: 4. After Graph Optimization"),
EA_AFTER_SPLIT_UNIQUE_TYPES_1( "EA: 5. After split_unique_types Phase 1"),
EA_AFTER_SPLIT_UNIQUE_TYPES_3( "EA: 5. After split_unique_types Phase 3"),
EA_AFTER_SPLIT_UNIQUE_TYPES_4( "EA: 5. After split_unique_types Phase 4"),
EA_AFTER_SPLIT_UNIQUE_TYPES( "EA: 5. After split_unique_types"),
EA_AFTER_REDUCE_PHI_ON_SAFEPOINTS( "EA: 6. After reduce_phi_on_safepoints"),
EA_BEFORE_PHI_REDUCTION( "EA: 5. Before Phi Reduction"),
EA_AFTER_PHI_CASTPP_REDUCTION( "EA: 5. Phi -> CastPP Reduction"),
EA_AFTER_PHI_ADDP_REDUCTION( "EA: 5. Phi -> AddP Reduction"),
EA_AFTER_PHI_CMP_REDUCTION( "EA: 5. Phi -> Cmp Reduction"),
AFTER_EA( "After Escape Analysis"),
ITER_GVN_AFTER_EA( "Iter GVN after EA"),
BEFORE_BEAUTIFY_LOOPS( "Before Beautify Loops"),