| /* | |
| * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| * | |
| */ | |
| #ifdef COMPILER2 | |
| #include "peephole_x86_64.hpp" | |
| #include "adfiles/ad_x86.hpp" | |
| // This function transforms the shapes | |
| // mov d, s1; add d, s2 into | |
| // lea d, [s1 + s2] and | |
| // mov d, s1; shl d, s2 into | |
| // lea d, [s1 << s2] with s2 = 1, 2, 3 | |
| static bool lea_coalesce_helper(Block* block, int block_index, PhaseCFG* cfg_, PhaseRegAlloc* ra_, | |
| MachNode* (*new_root)(), uint inst0_rule, bool imm) { | |
| MachNode* inst0 = block->get_node(block_index)->as_Mach(); | |
| assert(inst0->rule() == inst0_rule, "sanity"); | |
| OptoReg::Name dst = ra_->get_reg_first(inst0); | |
| MachNode* inst1 = nullptr; | |
| OptoReg::Name src1 = OptoReg::Bad; | |
| if (inst0->in(1)->is_MachSpillCopy()) { | |
| OptoReg::Name in = ra_->get_reg_first(inst0->in(1)->in(1)); | |
| if (OptoReg::is_reg(in) && OptoReg::as_VMReg(in)->is_Register()) { | |
| inst1 = inst0->in(1)->as_Mach(); | |
| src1 = in; | |
| } | |
| } | |
| if (inst1 == nullptr) { | |
| return false; | |
| } | |
| assert(dst != src1, ""); | |
| // Only coalesce if inst1 is immediately followed by inst0 | |
| // Can be improved for more general cases | |
| if (block_index < 1 || block->get_node(block_index - 1) != inst1) { | |
| return false; | |
| } | |
| int inst1_index = block_index - 1; | |
| Node* inst2; | |
| if (imm) { | |
| inst2 = nullptr; | |
| } else { | |
| inst2 = inst0->in(2); | |
| if (inst2 == inst1) { | |
| inst2 = inst2->in(1); | |
| } | |
| } | |
| // See VM_Version::supports_fast_3op_lea() | |
| if (!imm) { | |
| Register rsrc1 = OptoReg::as_VMReg(src1)->as_Register(); | |
| Register rsrc2 = OptoReg::as_VMReg(ra_->get_reg_first(inst2))->as_Register(); | |
| if ((rsrc1 == rbp || rsrc1 == r13) && (rsrc2 == rbp || rsrc2 == r13)) { | |
| return false; | |
| } | |
| } | |
| // Go down the block to find the output proj node (the flag output) of inst0 | |
| int proj_index = -1; | |
| Node* proj = nullptr; | |
| for (uint pos = block_index + 1; pos < block->number_of_nodes(); pos++) { | |
| Node* curr = block->get_node(pos); | |
| if (curr->is_MachProj() && curr->in(0) == inst0) { | |
| proj_index = pos; | |
| proj = curr; | |
| break; | |
| } | |
| } | |
| assert(proj != nullptr, ""); | |
| // If some node uses the flag, cannot remove | |
| if (proj->outcnt() > 0) { | |
| return false; | |
| } | |
| MachNode* root = new_root(); | |
| // Assign register for the newly allocated node | |
| ra_->set_oop(root, ra_->is_oop(inst0)); | |
| ra_->set_pair(root->_idx, ra_->get_reg_second(inst0), ra_->get_reg_first(inst0)); | |
| // Set input and output for the node | |
| root->add_req(inst0->in(0)); | |
| root->add_req(inst1->in(1)); | |
| // No input for constant after matching | |
| if (!imm) { | |
| root->add_req(inst2); | |
| } | |
| inst0->replace_by(root); | |
| proj->set_req(0, inst0); | |
| // Initialize the operand array | |
| root->_opnds[0] = inst0->_opnds[0]->clone(); | |
| root->_opnds[1] = inst0->_opnds[1]->clone(); | |
| root->_opnds[2] = inst0->_opnds[2]->clone(); | |
| // Modify the block | |
| inst0->set_removed(); | |
| inst1->set_removed(); | |
| block->remove_node(proj_index); | |
| block->remove_node(block_index); | |
| block->remove_node(inst1_index); | |
| block->insert_node(root, block_index - 1); | |
| // Modify the CFG | |
| cfg_->map_node_to_block(inst0, nullptr); | |
| cfg_->map_node_to_block(inst1, nullptr); | |
| cfg_->map_node_to_block(proj, nullptr); | |
| cfg_->map_node_to_block(root, block); | |
| return true; | |
| } | |
| // This helper func takes a condition and returns the flags that need to be set for the condition | |
| // It uses the same flags as the test instruction, so if the e.g. the overflow bit is required, | |
| // this func returns clears_overflow, as that is what the test instruction does and what the downstream path expects | |
| static juint map_condition_to_required_test_flags(Assembler::Condition condition) { | |
| switch (condition) { | |
| case Assembler::Condition::zero: // Same value as equal | |
| case Assembler::Condition::notZero: // Same value as notEqual | |
| return Node::PD::Flag_sets_zero_flag; | |
| case Assembler::Condition::less: | |
| case Assembler::Condition::greaterEqual: | |
| return Node::PD::Flag_sets_sign_flag | Node::PD::Flag_clears_overflow_flag; | |
| case Assembler::Condition::lessEqual: | |
| case Assembler::Condition::greater: | |
| return Node::PD::Flag_sets_sign_flag | Node::PD::Flag_clears_overflow_flag | Node::PD::Flag_sets_zero_flag; | |
| case Assembler::Condition::below: // Same value as carrySet | |
| case Assembler::Condition::aboveEqual: // Same value as carryClear | |
| return Node::PD::Flag_clears_carry_flag; | |
| case Assembler::Condition::belowEqual: | |
| case Assembler::Condition::above: | |
| return Node::PD::Flag_clears_carry_flag | Node::PD::Flag_sets_zero_flag; | |
| case Assembler::Condition::overflow: | |
| case Assembler::Condition::noOverflow: | |
| return Node::PD::Flag_clears_overflow_flag; | |
| case Assembler::Condition::negative: | |
| case Assembler::Condition::positive: | |
| return Node::PD::Flag_sets_sign_flag; | |
| case Assembler::Condition::parity: | |
| case Assembler::Condition::noParity: | |
| return Node::PD::Flag_sets_parity_flag; | |
| default: | |
| ShouldNotReachHere(); | |
| return 0; | |
| } | |
| } | |
| // This function removes the TEST instruction when it detected shapes likes AND r1, r2; TEST r1, r1 | |
| // It checks the required EFLAGS for the downstream instructions of the TEST | |
| // and removes the TEST if the preceding instructions already sets all these flags | |
| bool Peephole::test_may_remove(Block* block, int block_index, PhaseCFG* cfg_, PhaseRegAlloc* ra_, | |
| MachNode* (*new_root)(), uint inst0_rule) { | |
| MachNode* test_to_check = block->get_node(block_index)->as_Mach(); | |
| assert(test_to_check->rule() == inst0_rule, "sanity"); | |
| Node* inst1 = test_to_check->in(1); | |
| // Only remove test if the block order is inst1 -> MachProjNode (because the node to match must specify KILL cr) -> test_to_check | |
| // So inst1 must be at index - 2 | |
| if (block_index < 2 || block->get_node(block_index - 2) != inst1) { | |
| return false; | |
| } | |
| if (inst1 != nullptr) { | |
| MachNode* prevNode = inst1->isa_Mach(); | |
| if (prevNode != nullptr) { | |
| // Includes other flags as well, but that doesn't matter here | |
| juint all_node_flags = prevNode->flags(); | |
| if (all_node_flags == 0) { | |
| // We can return early - there is no way the test can be removed, the preceding node does not set any flags | |
| return false; | |
| } | |
| juint required_flags = 0; | |
| // Search for the uses of the node and compute which flags are required | |
| for (DUIterator_Fast imax, i = test_to_check->fast_outs(imax); i < imax; i++) { | |
| MachNode* node_out = test_to_check->fast_out(i)->isa_Mach(); | |
| bool found_correct_oper = false; | |
| for (uint16_t j = 0; j < node_out->_num_opnds; ++j) { | |
| MachOper* operand = node_out->_opnds[j]; | |
| if (operand->opcode() == cmpOp_rule || operand->opcode() == cmpOpU_rule) { | |
| auto condition = static_cast<Assembler::Condition>(operand->ccode()); | |
| juint flags_for_inst = map_condition_to_required_test_flags(condition); | |
| required_flags = required_flags | flags_for_inst; | |
| found_correct_oper = true; | |
| break; | |
| } | |
| } | |
| if (!found_correct_oper) { | |
| // We could not find one the required flags for one of the dependencies. Keep the test as it might set flags needed for that node | |
| return false; | |
| } | |
| } | |
| assert(required_flags != 0, "No flags required, should be impossible!"); | |
| bool sets_all_required_flags = (required_flags & ~all_node_flags) == 0; | |
| if (sets_all_required_flags) { | |
| // All flags are covered are clear to remove this test | |
| MachProjNode* machProjNode = block->get_node(block_index - 1)->isa_MachProj(); | |
| assert(machProjNode != nullptr, "Expected a MachProj node here!"); | |
| assert(ra_->get_reg_first(machProjNode) == ra_->get_reg_first(test_to_check), "Test must operate on the same register as its replacement"); | |
| // Remove the original test node and replace it with the pseudo test node. The AND node already sets ZF | |
| test_to_check->replace_by(machProjNode); | |
| // Modify the block | |
| test_to_check->set_removed(); | |
| block->remove_node(block_index); | |
| // Modify the control flow | |
| cfg_->map_node_to_block(test_to_check, nullptr); | |
| return true; | |
| } | |
| } | |
| } | |
| return false; | |
| } | |
| bool Peephole::lea_coalesce_reg(Block* block, int block_index, PhaseCFG* cfg_, PhaseRegAlloc* ra_, | |
| MachNode* (*new_root)(), uint inst0_rule) { | |
| return lea_coalesce_helper(block, block_index, cfg_, ra_, new_root, inst0_rule, false); | |
| } | |
| bool Peephole::lea_coalesce_imm(Block* block, int block_index, PhaseCFG* cfg_, PhaseRegAlloc* ra_, | |
| MachNode* (*new_root)(), uint inst0_rule) { | |
| return lea_coalesce_helper(block, block_index, cfg_, ra_, new_root, inst0_rule, true); | |
| } | |
| #endif // COMPILER2 |