-
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
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 |
|---|---|---|
|
|
@@ -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,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 { | ||
|
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); | ||
| } | ||
|
|
@@ -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.)); | ||
|
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. Can we replace all the other usages of 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. Yes, that sounds like a good idea. One still need to build the right 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. 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"); | ||
marc-chevalier marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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; | ||
| } | ||
|
|
||
| //============================================================================= | ||
|
|
||
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::Parmsstands 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...