blob: 2197055d1ecc0a9005842050b0f1ac86bc2a06af [file] [edit]
/*
* 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