-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8347901: C2 should remove unused leaf / pure runtime calls #25760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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; | ||||||
|
@@ -1303,6 +1303,53 @@ void CallLeafVectorNode::calling_convention( BasicType* sig_bt, VMRegPair *parm_ | |||||
|
||||||
|
||||||
//============================================================================= | ||||||
bool CallLeafPureNode::is_unused() const { | ||||||
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 leave that to IGVN | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
* 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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you make sure that this method is called from all its subclasses? It seems to me that you just copied the code to the subclasses, rather than calling this method, am I right? |
||||||
if (is_dead()) { // dead node | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The comment seemed redundant. You could say who else is responsible of cleaning up the dead node though. What would happen if the |
||||||
return nullptr; | ||||||
} | ||||||
|
||||||
if (can_reshape && is_unused()) { | ||||||
return make_tuple_of_input_state_and_top_return_values(phase->C); | ||||||
} | ||||||
marc-chevalier marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
return CallRuntimeNode::Ideal(phase, can_reshape); | ||||||
} | ||||||
|
||||||
#ifndef PRODUCT | ||||||
void CallLeafNode::dump_spec(outputStream *st) const { | ||||||
st->print("# "); | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
||
|
@@ -913,6 +913,22 @@ class CallLeafNode : public CallRuntimeNode { | |
#endif | ||
}; | ||
|
||
class CallLeafPureNode : public CallLeafNode { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
} | ||
|
@@ -1520,12 +1520,14 @@ Node* ModFNode::Ideal(PhaseGVN* phase, bool can_reshape) { | |
if (!can_reshape) { | ||
return nullptr; | ||
} | ||
Comment on lines
1520
to
1522
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this prevent us from doing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, you should probably call |
||
if (is_dead()) { // dead node | ||
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.)); | ||
if (is_unused()) { | ||
return make_tuple_of_input_state_and_top_return_values(igvn->C); | ||
marc-chevalier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// Either input is TOP ==> the result is TOP | ||
|
@@ -1572,12 +1574,14 @@ Node* ModDNode::Ideal(PhaseGVN* phase, bool can_reshape) { | |
if (!can_reshape) { | ||
return nullptr; | ||
} | ||
if (is_dead()) { // dead node | ||
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.)); | ||
if (is_unused()) { | ||
return make_tuple_of_input_state_and_top_return_values(igvn->C); | ||
} | ||
|
||
// Either input is TOP ==> the result is TOP | ||
|
@@ -1626,20 +1630,6 @@ Node* ModFloatingNode::replace_with_con(PhaseIterGVN* phase, const Type* 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"); | ||
marc-chevalier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (projs.resproj != nullptr) { | ||
phase->replace_node(projs.resproj, con_node); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -120,6 +120,10 @@ const TypePtr *ProjNode::adr_type() const { | |
if (bottom_type() == Type::MEMORY) { | ||
// in(0) might be a narrow MemBar; otherwise we will report TypePtr::BOTTOM | ||
Node* ctrl = in(0); | ||
if (ctrl->Opcode() == Op_Tuple) { | ||
// Jumping over Tuples: the i-th projection of a Tuple is the i-th input of the Tuple. | ||
ctrl = ctrl->in(_con); | ||
} | ||
Comment on lines
+123
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need to special-case this here? Why does the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is a good question. That is something I picked from Vladimir's implementation and it seemed legitimate. But now you say it, is it needed? Not sure. I'm trying to find that out. Would I'm trying to understand what happens if we don't have that. But maybe @iwanowww would have some helpful insight? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't remember all the details now, but there were some problems when |
||
if (ctrl == nullptr) return nullptr; // node is dead | ||
const TypePtr* adr_type = ctrl->adr_type(); | ||
#ifdef ASSERT | ||
|
@@ -163,6 +167,15 @@ void ProjNode::check_con() const { | |
assert(_con < t->is_tuple()->cnt(), "ProjNode::_con must be in range"); | ||
} | ||
|
||
//------------------------------Identity--------------------------------------- | ||
Node* ProjNode::Identity(PhaseGVN* phase) { | ||
if (in(0) != nullptr && in(0)->Opcode() == Op_Tuple) { | ||
// Jumping over Tuples: the i-th projection of a Tuple is the i-th input of the Tuple. | ||
return in(0)->in(_con); | ||
} | ||
return this; | ||
} | ||
marc-chevalier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
//------------------------------Value------------------------------------------ | ||
const Type* ProjNode::Value(PhaseGVN* phase) const { | ||
if (in(0) == nullptr) return Type::TOP; | ||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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
.There was a problem hiding this comment.
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 🤣There was a problem hiding this comment.
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...