8360389: Support printing from C2 compiled code

Reviewed-by: kvn, thartmann, mhaessig
This commit is contained in:
Benoît Maillard 2025-10-07 07:43:43 +00:00 committed by Tobias Hartmann
parent e783c524c1
commit 07549f3e15
6 changed files with 317 additions and 0 deletions

View File

@ -5378,3 +5378,158 @@ Node* Compile::narrow_value(BasicType bt, Node* value, const Type* type, PhaseGV
void Compile::record_method_not_compilable_oom() {
record_method_not_compilable(CompilationMemoryStatistic::failure_reason_memlimit());
}
#ifndef PRODUCT
// Collects all the control inputs from nodes on the worklist and from their data dependencies
static void find_candidate_control_inputs(Unique_Node_List& worklist, Unique_Node_List& candidates) {
// Follow non-control edges until we reach CFG nodes
for (uint i = 0; i < worklist.size(); i++) {
const Node* n = worklist.at(i);
for (uint j = 0; j < n->req(); j++) {
Node* in = n->in(j);
if (in == nullptr || in->is_Root()) {
continue;
}
if (in->is_CFG()) {
if (in->is_Call()) {
// The return value of a call is only available if the call did not result in an exception
Node* control_proj_use = in->as_Call()->proj_out(TypeFunc::Control)->unique_out();
if (control_proj_use->is_Catch()) {
Node* fall_through = control_proj_use->as_Catch()->proj_out(CatchProjNode::fall_through_index);
candidates.push(fall_through);
continue;
}
}
if (in->is_Multi()) {
// We got here by following data inputs so we should only have one control use
// (no IfNode, etc)
assert(!n->is_MultiBranch(), "unexpected node type: %s", n->Name());
candidates.push(in->as_Multi()->proj_out(TypeFunc::Control));
} else {
candidates.push(in);
}
} else {
worklist.push(in);
}
}
}
}
// Returns the candidate node that is a descendant to all the other candidates
static Node* pick_control(Unique_Node_List& candidates) {
Unique_Node_List worklist;
worklist.copy(candidates);
// Traverse backwards through the CFG
for (uint i = 0; i < worklist.size(); i++) {
const Node* n = worklist.at(i);
if (n->is_Root()) {
continue;
}
for (uint j = 0; j < n->req(); j++) {
// Skip backedge of loops to avoid cycles
if (n->is_Loop() && j == LoopNode::LoopBackControl) {
continue;
}
Node* pred = n->in(j);
if (pred != nullptr && pred != n && pred->is_CFG()) {
worklist.push(pred);
// if pred is an ancestor of n, then pred is an ancestor to at least one candidate
candidates.remove(pred);
}
}
}
assert(candidates.size() == 1, "unexpected control flow");
return candidates.at(0);
}
// Initialize a parameter input for a debug print call, using a placeholder for jlong and jdouble
static void debug_print_init_parm(Node* call, Node* parm, Node* half, int* pos) {
call->init_req((*pos)++, parm);
const BasicType bt = parm->bottom_type()->basic_type();
if (bt == T_LONG || bt == T_DOUBLE) {
call->init_req((*pos)++, half);
}
}
Node* Compile::make_debug_print_call(const char* str, address call_addr, PhaseGVN* gvn,
Node* parm0, Node* parm1,
Node* parm2, Node* parm3,
Node* parm4, Node* parm5,
Node* parm6) const {
Node* str_node = gvn->transform(new ConPNode(TypeRawPtr::make(((address) str))));
const TypeFunc* type = OptoRuntime::debug_print_Type(parm0, parm1, parm2, parm3, parm4, parm5, parm6);
Node* call = new CallLeafNode(type, call_addr, "debug_print", TypeRawPtr::BOTTOM);
// find the most suitable control input
Unique_Node_List worklist, candidates;
if (parm0 != nullptr) { worklist.push(parm0);
if (parm1 != nullptr) { worklist.push(parm1);
if (parm2 != nullptr) { worklist.push(parm2);
if (parm3 != nullptr) { worklist.push(parm3);
if (parm4 != nullptr) { worklist.push(parm4);
if (parm5 != nullptr) { worklist.push(parm5);
if (parm6 != nullptr) { worklist.push(parm6);
/* close each nested if ===> */ } } } } } } }
find_candidate_control_inputs(worklist, candidates);
Node* control = nullptr;
if (candidates.size() == 0) {
control = C->start()->proj_out(TypeFunc::Control);
} else {
control = pick_control(candidates);
}
// find all the previous users of the control we picked
GrowableArray<Node*> users_of_control;
for (DUIterator_Fast kmax, i = control->fast_outs(kmax); i < kmax; i++) {
Node* use = control->fast_out(i);
if (use->is_CFG() && use != control) {
users_of_control.push(use);
}
}
// we do not actually care about IO and memory as it uses neither
call->init_req(TypeFunc::Control, control);
call->init_req(TypeFunc::I_O, top());
call->init_req(TypeFunc::Memory, top());
call->init_req(TypeFunc::FramePtr, C->start()->proj_out(TypeFunc::FramePtr));
call->init_req(TypeFunc::ReturnAdr, top());
int pos = TypeFunc::Parms;
call->init_req(pos++, str_node);
if (parm0 != nullptr) { debug_print_init_parm(call, parm0, top(), &pos);
if (parm1 != nullptr) { debug_print_init_parm(call, parm1, top(), &pos);
if (parm2 != nullptr) { debug_print_init_parm(call, parm2, top(), &pos);
if (parm3 != nullptr) { debug_print_init_parm(call, parm3, top(), &pos);
if (parm4 != nullptr) { debug_print_init_parm(call, parm4, top(), &pos);
if (parm5 != nullptr) { debug_print_init_parm(call, parm5, top(), &pos);
if (parm6 != nullptr) { debug_print_init_parm(call, parm6, top(), &pos);
/* close each nested if ===> */ } } } } } } }
assert(call->in(call->req()-1) != nullptr, "must initialize all parms");
call = gvn->transform(call);
Node* call_control_proj = gvn->transform(new ProjNode(call, TypeFunc::Control));
// rewire previous users to have the new call as control instead
PhaseIterGVN* igvn = gvn->is_IterGVN();
for (int i = 0; i < users_of_control.length(); i++) {
Node* use = users_of_control.at(i);
for (uint j = 0; j < use->req(); j++) {
if (use->in(j) == control) {
if (igvn != nullptr) {
igvn->replace_input_of(use, j, call_control_proj);
} else {
gvn->hash_delete(use);
use->set_req(j, call_control_proj);
gvn->hash_insert(use);
}
}
}
}
return call;
}
#endif // !PRODUCT

View File

@ -1316,6 +1316,28 @@ public:
BasicType out_bt, BasicType in_bt);
static Node* narrow_value(BasicType bt, Node* value, const Type* type, PhaseGVN* phase, bool transform_res);
#ifndef PRODUCT
private:
// getting rid of the template makes things easier
Node* make_debug_print_call(const char* str, address call_addr, PhaseGVN* gvn,
Node* parm0 = nullptr, Node* parm1 = nullptr,
Node* parm2 = nullptr, Node* parm3 = nullptr,
Node* parm4 = nullptr, Node* parm5 = nullptr,
Node* parm6 = nullptr) const;
public:
// Creates a CallLeafNode for a runtime call that prints a static string and the values of the
// nodes passed as arguments.
// This function also takes care of doing the necessary wiring, including finding a suitable control
// based on the nodes that need to be printed. Note that passing nodes that have incompatible controls
// is undefined behavior.
template <typename... TT, typename... NN>
Node* make_debug_print(const char* str, PhaseGVN* gvn, NN... in) {
address call_addr = CAST_FROM_FN_PTR(address, SharedRuntime::debug_print<TT...>);
return make_debug_print_call(str, call_addr, gvn, in...);
}
#endif
};
#endif // SHARE_OPTO_COMPILE_HPP

View File

@ -1780,6 +1780,62 @@ static const TypeFunc* make_osr_end_Type() {
return TypeFunc::make(domain, range);
}
#ifndef PRODUCT
static void debug_print_convert_type(const Type** fields, int* argp, Node *parm) {
const BasicType bt = parm->bottom_type()->basic_type();
fields[(*argp)++] = Type::get_const_basic_type(bt);
if (bt == T_LONG || bt == T_DOUBLE) {
fields[(*argp)++] = Type::HALF;
}
}
static void update_arg_cnt(const Node* parm, int* arg_cnt) {
(*arg_cnt)++;
const BasicType bt = parm->bottom_type()->basic_type();
if (bt == T_LONG || bt == T_DOUBLE) {
(*arg_cnt)++;
}
}
const TypeFunc* OptoRuntime::debug_print_Type(Node* parm0, Node* parm1,
Node* parm2, Node* parm3,
Node* parm4, Node* parm5,
Node* parm6) {
int argcnt = 1;
if (parm0 != nullptr) { update_arg_cnt(parm0, &argcnt);
if (parm1 != nullptr) { update_arg_cnt(parm1, &argcnt);
if (parm2 != nullptr) { update_arg_cnt(parm2, &argcnt);
if (parm3 != nullptr) { update_arg_cnt(parm3, &argcnt);
if (parm4 != nullptr) { update_arg_cnt(parm4, &argcnt);
if (parm5 != nullptr) { update_arg_cnt(parm5, &argcnt);
if (parm6 != nullptr) { update_arg_cnt(parm6, &argcnt);
/* close each nested if ===> */ } } } } } } }
// create input type (domain)
const Type** fields = TypeTuple::fields(argcnt);
int argp = TypeFunc::Parms;
fields[argp++] = TypePtr::NOTNULL; // static string pointer
if (parm0 != nullptr) { debug_print_convert_type(fields, &argp, parm0);
if (parm1 != nullptr) { debug_print_convert_type(fields, &argp, parm1);
if (parm2 != nullptr) { debug_print_convert_type(fields, &argp, parm2);
if (parm3 != nullptr) { debug_print_convert_type(fields, &argp, parm3);
if (parm4 != nullptr) { debug_print_convert_type(fields, &argp, parm4);
if (parm5 != nullptr) { debug_print_convert_type(fields, &argp, parm5);
if (parm6 != nullptr) { debug_print_convert_type(fields, &argp, parm6);
/* close each nested if ===> */ } } } } } } }
assert(argp == TypeFunc::Parms+argcnt, "correct decoding");
const TypeTuple* domain = TypeTuple::make(TypeFunc::Parms+argcnt, fields);
// no result type needed
fields = TypeTuple::fields(1);
fields[TypeFunc::Parms+0] = nullptr; // void
const TypeTuple* range = TypeTuple::make(TypeFunc::Parms, fields);
return TypeFunc::make(domain, range);
}
#endif // PRODUCT
//-------------------------------------------------------------------------------------
// register policy

View File

@ -737,6 +737,16 @@ private:
return _dtrace_object_alloc_Type;
}
#ifndef PRODUCT
// Signature for runtime calls in debug printing nodes, which depends on which nodes are actually passed
// Note: we do not allow more than 7 node arguments as GraphKit::make_runtime_call only allows 8, and we need
// one for the static string
static const TypeFunc* debug_print_Type(Node* parm0 = nullptr, Node* parm1 = nullptr,
Node* parm2 = nullptr, Node* parm3 = nullptr,
Node* parm4 = nullptr, Node* parm5 = nullptr,
Node* parm6 = nullptr);
#endif // PRODUCT
private:
static NamedCounter * volatile _named_counters;

View File

@ -264,6 +264,46 @@ void SharedRuntime::print_ic_miss_histogram() {
tty->print_cr("Total IC misses: %7d", tot_misses);
}
}
#ifdef COMPILER2
// Runtime methods for printf-style debug nodes (same printing format as fieldDescriptor::print_on_for)
void SharedRuntime::debug_print_value(jboolean x) {
tty->print_cr("boolean %d", x);
}
void SharedRuntime::debug_print_value(jbyte x) {
tty->print_cr("byte %d", x);
}
void SharedRuntime::debug_print_value(jshort x) {
tty->print_cr("short %d", x);
}
void SharedRuntime::debug_print_value(jchar x) {
tty->print_cr("char %c %d", isprint(x) ? x : ' ', x);
}
void SharedRuntime::debug_print_value(jint x) {
tty->print_cr("int %d", x);
}
void SharedRuntime::debug_print_value(jlong x) {
tty->print_cr("long " JLONG_FORMAT, x);
}
void SharedRuntime::debug_print_value(jfloat x) {
tty->print_cr("float %f", x);
}
void SharedRuntime::debug_print_value(jdouble x) {
tty->print_cr("double %lf", x);
}
void SharedRuntime::debug_print_value(oopDesc* x) {
x->print();
}
#endif // COMPILER2
#endif // PRODUCT

View File

@ -32,6 +32,7 @@
#include "memory/allStatic.hpp"
#include "memory/metaspaceClosure.hpp"
#include "memory/resourceArea.hpp"
#include "runtime/safepointVerifiers.hpp"
#include "runtime/stubInfo.hpp"
#include "utilities/macros.hpp"
@ -635,6 +636,39 @@ class SharedRuntime: AllStatic {
static void print_call_statistics(uint64_t comp_total);
static void print_ic_miss_histogram();
#ifdef COMPILER2
// Runtime methods for printf-style debug nodes
static void debug_print_value(jboolean x);
static void debug_print_value(jbyte x);
static void debug_print_value(jshort x);
static void debug_print_value(jchar x);
static void debug_print_value(jint x);
static void debug_print_value(jlong x);
static void debug_print_value(jfloat x);
static void debug_print_value(jdouble x);
static void debug_print_value(oopDesc* x);
template <typename T, typename... Rest>
static void debug_print_rec(T arg, Rest... args) {
debug_print_value(arg);
debug_print_rec(args...);
}
static void debug_print_rec() {}
// template is required here as we need to know the exact signature at compile-time
template <typename... TT>
static void debug_print(const char *str, TT... args) {
// these three lines are the manual expansion of JRT_LEAF ... JRT_END, does not work well with templates
DEBUG_ONLY(NoHandleMark __hm;)
os::verify_stack_alignment();
DEBUG_ONLY(NoSafepointVerifier __nsv;)
tty->print_cr("%s", str);
debug_print_rec(args...);
}
#endif // COMPILER2
#endif // PRODUCT
static void print_statistics() PRODUCT_RETURN;