Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion src/hotspot/share/opto/callnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,7 @@ Node *CallNode::result_cast() {
}


void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts) {
void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts) const {
projs->fallthrough_proj = nullptr;
projs->fallthrough_catchproj = nullptr;
projs->fallthrough_ioproj = nullptr;
Expand Down Expand Up @@ -1303,6 +1303,57 @@ void CallLeafVectorNode::calling_convention( BasicType* sig_bt, VMRegPair *parm_


//=============================================================================
bool CallLeafPureNode::is_unused() const {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a quick comment why this check implies that the node is not used, i.e. what that means?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think i'll need you to explain to me what is unclear at the moment. When I read the function, I see:
"A CallLeafPure is unused iff there is no output result projection."

I don't see what else to add that is not covered by "if we don't use the result, the pure call is unused", which is exactly the code. Is there any untold hypothesis lurking somewhere that I don't see? It seems to me it uses just very common concepts of C2.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The call could have other uses for other projections. Why does this projection make it unused?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose I was not aware that TypeFunc::Parms stands for result projection.... the name does not make it immediately apparent.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I think I'd be better to comment on the declaration of the class then. Something saying that CallLeafPureNode represents calls that are pure: they only have data input and output data (and control for practical reasons for now), no exceptions, no memory, no safepoint... They can be freely be moved around, duplicated or, if the result isn't used, removed. Then that explains... a lot of what we are doing, not just is_unused.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I was really just confused about the Parms. I thought that means parameters .. and not results 🤣

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually both. For functions, the parameters are starting at Parms, and the results too. Before that, it's all the special stuff: control, memory, io...

return proj_out_or_null(TypeFunc::Parms) == nullptr;
}

bool CallLeafPureNode::is_dead() const {
return proj_out_or_null(TypeFunc::Control) == nullptr;
}

/* We make a tuple of the global input state + TOP for the output values.
* We use this to delete a pure function that is not used: by replacing the call with
* such a tuple, we let output Proj's idealization pick the corresponding input of the
* pure call, so jumping over it, and effectively, removing the call from the graph.
* This avoids doing the graph surgery manually, but leaves that to IGVN
* that is specialized for doing that right. We need also tuple components for output
* values of the function to respect the return arity, and in case there is a projection
* that would pick an output (which shouldn't happen at the moment).
*/
TupleNode* CallLeafPureNode::make_tuple_of_input_state_and_top_return_values(const Compile* C) const {
// Transparently propagate input state but parameters
TupleNode* tuple = TupleNode::make(
tf()->range(),
in(TypeFunc::Control),
in(TypeFunc::I_O),
in(TypeFunc::Memory),
in(TypeFunc::FramePtr),
in(TypeFunc::ReturnAdr));

// And add TOPs for the return values
for (uint i = TypeFunc::Parms; i < tf()->range()->cnt(); i++) {
tuple->set_req(i, C->top());
}

return tuple;
}

Node* CallLeafPureNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (is_dead()) {
return nullptr;
}

// We need to wait until IGVN because during parsing, usages might still be missing
// and we would remove the call immediately.
if (can_reshape && is_unused()) {
// The result is not used. We remove the call by replacing it with a tuple, that
// is later disintegrated by the projections.
return make_tuple_of_input_state_and_top_return_values(phase->C);
}

return CallRuntimeNode::Ideal(phase, can_reshape);
}

#ifndef PRODUCT
void CallLeafNode::dump_spec(outputStream *st) const {
st->print("# ");
Expand Down
29 changes: 28 additions & 1 deletion src/hotspot/share/opto/callnode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -737,7 +737,7 @@ class CallNode : public SafePointNode {
// Collect all the interesting edges from a call for use in
// replacing the call by something else. Used by macro expansion
// and the late inlining support.
void extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts = true);
void extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts = true) const;

virtual uint match_edge(uint idx) const;

Expand Down Expand Up @@ -913,6 +913,33 @@ class CallLeafNode : public CallRuntimeNode {
#endif
};

/* A pure function call, they are assumed not to be safepoints, not to read or write memory,
* have no exception... They just take parameters, return a value without side effect. It is
* always correct to create some, or remove them, if the result is not used.
*
* They still have control input to allow easy lowering into other kind of calls that require
* a control, but this is more a technical than a moral constraint.
*
* Pure calls must have only control and data input and output: I/O, Memory and so on must be top.
* Nevertheless, pure calls can typically be expensive math operations so care must be taken
* when letting the node float.
*/
class CallLeafPureNode : public CallLeafNode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need a short description about what this node is for. What are the assumptions about it?

protected:
bool is_unused() const;
bool is_dead() const;
TupleNode* make_tuple_of_input_state_and_top_return_values(const Compile* C) const;

public:
CallLeafPureNode(const TypeFunc* tf, address addr, const char* name,
const TypePtr* adr_type)
: CallLeafNode(tf, addr, name, adr_type) {
init_class_id(Class_CallLeafPure);
}
int Opcode() const override;
Node* Ideal(PhaseGVN* phase, bool can_reshape) override;
};

//------------------------------CallLeafNoFPNode-------------------------------
// CallLeafNode, not using floating point or using it in the same manner as
// the generated code
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/opto/classes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ macro(CallDynamicJava)
macro(CallJava)
macro(CallLeaf)
macro(CallLeafNoFP)
macro(CallLeafPure)
macro(CallLeafVector)
macro(CallRuntime)
macro(CallStaticJava)
Expand Down Expand Up @@ -372,6 +373,7 @@ macro(SubI)
macro(SubL)
macro(TailCall)
macro(TailJump)
macro(Tuple)
macro(MacroLogicV)
macro(ThreadLocal)
macro(Unlock)
Expand Down
19 changes: 19 additions & 0 deletions src/hotspot/share/opto/compile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3298,6 +3298,25 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f
case Op_Opaque1: // Remove Opaque Nodes before matching
n->subsume_by(n->in(1), this);
break;
case Op_CallLeafPure: {
// If the pure call is not supported, then lower to a CallLeaf.
if (!Matcher::match_rule_supported(Op_CallLeafPure)) {
CallNode* call = n->as_Call();
CallNode* new_call = new CallLeafNode(call->tf(), call->entry_point(),
call->_name, TypeRawPtr::BOTTOM);
new_call->init_req(TypeFunc::Control, call->in(TypeFunc::Control));
new_call->init_req(TypeFunc::I_O, C->top());
new_call->init_req(TypeFunc::Memory, C->top());
new_call->init_req(TypeFunc::ReturnAdr, C->top());
new_call->init_req(TypeFunc::FramePtr, C->top());
for (unsigned int i = TypeFunc::Parms; i < call->tf()->domain()->cnt(); i++) {
new_call->init_req(i, call->in(i));
}
n->subsume_by(new_call, this);
}
frc.inc_call_count();
break;
}
case Op_CallStaticJava:
case Op_CallJava:
case Op_CallDynamicJava:
Expand Down
158 changes: 65 additions & 93 deletions src/hotspot/share/opto/divnode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,19 @@

#include <math.h>

ModFloatingNode::ModFloatingNode(Compile* C, const TypeFunc* tf, const char* name) : CallLeafNode(tf, nullptr, name, TypeRawPtr::BOTTOM) {
ModFloatingNode::ModFloatingNode(Compile* C, const TypeFunc* tf, address addr, const char* name) : CallLeafPureNode(tf, addr, name, TypeRawPtr::BOTTOM) {
add_flag(Flag_is_macro);
C->add_macro_node(this);
}

ModDNode::ModDNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::Math_DD_D_Type(), "drem") {
ModDNode::ModDNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::Math_DD_D_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::drem), "drem") {
init_req(TypeFunc::Parms + 0, a);
init_req(TypeFunc::Parms + 1, C->top());
init_req(TypeFunc::Parms + 2, b);
init_req(TypeFunc::Parms + 3, C->top());
}

ModFNode::ModFNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::modf_Type(), "frem") {
ModFNode::ModFNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::modf_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::frem), "frem") {
init_req(TypeFunc::Parms + 0, a);
init_req(TypeFunc::Parms + 1, b);
}
Expand Down Expand Up @@ -1516,137 +1516,109 @@ const Type* UModLNode::Value(PhaseGVN* phase) const {
return unsigned_mod_value<TypeLong, julong, jlong>(phase, this);
}

Node* ModFNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (!can_reshape) {
return nullptr;
}
PhaseIterGVN* igvn = phase->is_IterGVN();

bool result_is_unused = proj_out_or_null(TypeFunc::Parms) == nullptr;
bool not_dead = proj_out_or_null(TypeFunc::Control) != nullptr;
if (result_is_unused && not_dead) {
return replace_with_con(igvn, TypeF::make(0.));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we replace all the other usages of ModFloatingNode::replace_with_con by TupleNode and get rid of that method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that sounds like a good idea. One still need to build the right TupleNode, which takes a bit of code. So in detail, I'd rather replace replace_with_con with a function returning the right TupleNode to be as concise on the call-site.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that sounds good.

}

// Either input is TOP ==> the result is TOP
const Type* t1 = phase->type(dividend());
const Type* t2 = phase->type(divisor());
if (t1 == Type::TOP || t2 == Type::TOP) {
return phase->C->top();
}

const Type* ModFNode::get_result_if_constant(const Type* dividend, const Type* divisor) const {
// If either number is not a constant, we know nothing.
if ((t1->base() != Type::FloatCon) || (t2->base() != Type::FloatCon)) {
if ((dividend->base() != Type::FloatCon) || (divisor->base() != Type::FloatCon)) {
return nullptr; // note: x%x can be either NaN or 0
}

float f1 = t1->getf();
float f2 = t2->getf();
jint x1 = jint_cast(f1); // note: *(int*)&f1, not just (int)f1
jint x2 = jint_cast(f2);
float dividend_f = dividend->getf();
float divisor_f = divisor->getf();
jint dividend_i = jint_cast(dividend_f); // note: *(int*)&f1, not just (int)f1
jint divisor_i = jint_cast(divisor_f);

// If either is a NaN, return an input NaN
if (g_isnan(f1)) {
return replace_with_con(igvn, t1);
if (g_isnan(dividend_f)) {
return dividend;
}
if (g_isnan(f2)) {
return replace_with_con(igvn, t2);
if (g_isnan(divisor_f)) {
return divisor;
}

// If an operand is infinity or the divisor is +/- zero, punt.
if (!g_isfinite(f1) || !g_isfinite(f2) || x2 == 0 || x2 == min_jint) {
if (!g_isfinite(dividend_f) || !g_isfinite(divisor_f) || divisor_i == 0 || divisor_i == min_jint) {
return nullptr;
}

// We must be modulo'ing 2 float constants.
// Make sure that the sign of the fmod is equal to the sign of the dividend
jint xr = jint_cast(fmod(f1, f2));
if ((x1 ^ xr) < 0) {
jint xr = jint_cast(fmod(dividend_f, divisor_f));
if ((dividend_i ^ xr) < 0) {
xr ^= min_jint;
}

return replace_with_con(igvn, TypeF::make(jfloat_cast(xr)));
return TypeF::make(jfloat_cast(xr));
}

Node* ModDNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (!can_reshape) {
return nullptr;
}
PhaseIterGVN* igvn = phase->is_IterGVN();

bool result_is_unused = proj_out_or_null(TypeFunc::Parms) == nullptr;
bool not_dead = proj_out_or_null(TypeFunc::Control) != nullptr;
if (result_is_unused && not_dead) {
return replace_with_con(igvn, TypeD::make(0.));
}

// Either input is TOP ==> the result is TOP
const Type* t1 = phase->type(dividend());
const Type* t2 = phase->type(divisor());
if (t1 == Type::TOP || t2 == Type::TOP) {
return nullptr;
}

const Type* ModDNode::get_result_if_constant(const Type* dividend, const Type* divisor) const {
// If either number is not a constant, we know nothing.
if ((t1->base() != Type::DoubleCon) || (t2->base() != Type::DoubleCon)) {
if ((dividend->base() != Type::DoubleCon) || (divisor->base() != Type::DoubleCon)) {
return nullptr; // note: x%x can be either NaN or 0
}

double f1 = t1->getd();
double f2 = t2->getd();
jlong x1 = jlong_cast(f1); // note: *(long*)&f1, not just (long)f1
jlong x2 = jlong_cast(f2);
double dividend_d = dividend->getd();
double divisor_d = divisor->getd();
jlong dividend_l = jlong_cast(dividend_d); // note: *(long*)&f1, not just (long)f1
jlong divisor_l = jlong_cast(divisor_d);

// If either is a NaN, return an input NaN
if (g_isnan(f1)) {
return replace_with_con(igvn, t1);
if (g_isnan(dividend_d)) {
return dividend;
}
if (g_isnan(f2)) {
return replace_with_con(igvn, t2);
if (g_isnan(divisor_d)) {
return divisor;
}

// If an operand is infinity or the divisor is +/- zero, punt.
if (!g_isfinite(f1) || !g_isfinite(f2) || x2 == 0 || x2 == min_jlong) {
if (!g_isfinite(dividend_d) || !g_isfinite(divisor_d) || divisor_l == 0 || divisor_l == min_jlong) {
return nullptr;
}

// We must be modulo'ing 2 double constants.
// Make sure that the sign of the fmod is equal to the sign of the dividend
jlong xr = jlong_cast(fmod(f1, f2));
if ((x1 ^ xr) < 0) {
jlong xr = jlong_cast(fmod(dividend_d, divisor_d));
if ((dividend_l ^ xr) < 0) {
xr ^= min_jlong;
}

return replace_with_con(igvn, TypeD::make(jdouble_cast(xr)));
return TypeD::make(jdouble_cast(xr));
}

Node* ModFloatingNode::replace_with_con(PhaseIterGVN* phase, const Type* con) {
Compile* C = phase->C;
Node* con_node = phase->makecon(con);
CallProjections projs;
extract_projections(&projs, false, false);
phase->replace_node(projs.fallthrough_proj, in(TypeFunc::Control));
if (projs.fallthrough_catchproj != nullptr) {
phase->replace_node(projs.fallthrough_catchproj, in(TypeFunc::Control));
}
if (projs.fallthrough_memproj != nullptr) {
phase->replace_node(projs.fallthrough_memproj, in(TypeFunc::Memory));
}
if (projs.catchall_memproj != nullptr) {
phase->replace_node(projs.catchall_memproj, C->top());
}
if (projs.fallthrough_ioproj != nullptr) {
phase->replace_node(projs.fallthrough_ioproj, in(TypeFunc::I_O));
}
assert(projs.catchall_ioproj == nullptr, "no exceptions from floating mod");
assert(projs.catchall_catchproj == nullptr, "no exceptions from floating mod");
if (projs.resproj != nullptr) {
phase->replace_node(projs.resproj, con_node);
Node* ModFloatingNode::Ideal(PhaseGVN* phase, bool can_reshape) {
if (can_reshape) {
PhaseIterGVN* igvn = phase->is_IterGVN();

// Either input is TOP ==> the result is TOP
const Type* dividend_type = phase->type(dividend());
const Type* divisor_type = phase->type(divisor());
if (dividend_type == Type::TOP || divisor_type == Type::TOP) {
return phase->C->top();
}
const Type* constant_result = get_result_if_constant(dividend_type, divisor_type);
if (constant_result != nullptr) {
return make_tuple_of_input_state_and_constant_result(igvn, constant_result);
}
}
phase->replace_node(this, C->top());
C->remove_macro_node(this);
disconnect_inputs(C);
return nullptr;

return CallLeafPureNode::Ideal(phase, can_reshape);
}

/* Give a tuple node for ::Ideal to return, made of the input state (control to return addr)
* and the given constant result. Idealization of projections will make sure to transparently
* propagate the input state and replace the result by the said constant.
*/
TupleNode* ModFloatingNode::make_tuple_of_input_state_and_constant_result(PhaseIterGVN* phase, const Type* con) const {
Node* con_node = phase->makecon(con);
TupleNode* tuple = TupleNode::make(
tf()->range(),
in(TypeFunc::Control),
in(TypeFunc::I_O),
in(TypeFunc::Memory),
in(TypeFunc::FramePtr),
in(TypeFunc::ReturnAdr),
con_node);

return tuple;
}

//=============================================================================
Expand Down
Loading