blob: 83496f9d0be0a66b5cc0eb9f12a5d7967687ddef [file] [edit]
/*
* Copyright (c) 2024, 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.
*/
#include "opto/superwordVTransformBuilder.hpp"
#include "opto/vectornode.hpp"
void SuperWordVTransformBuilder::build() {
assert(!_packset.is_empty(), "must have non-empty packset");
assert(!_vtransform.has_graph(), "start with empty vtransform");
// Create vtnodes for all nodes in the loop.
build_vector_vtnodes_for_packed_nodes();
build_scalar_vtnodes_for_non_packed_nodes();
// Connect all vtnodes with their inputs. Possibly create vtnodes for input
// nodes that are outside the loop.
VectorSet vtn_memory_dependencies; // Shared, but cleared for every vtnode.
build_inputs_for_vector_vtnodes(vtn_memory_dependencies);
build_inputs_for_scalar_vtnodes(vtn_memory_dependencies);
}
void SuperWordVTransformBuilder::build_vector_vtnodes_for_packed_nodes() {
for (int i = 0; i < _packset.length(); i++) {
Node_List* pack = _packset.at(i);
VTransformVectorNode* vtn = make_vector_vtnode_for_pack(pack);
for (uint k = 0; k < pack->size(); k++) {
map_node_to_vtnode(pack->at(k), vtn);
}
}
}
void SuperWordVTransformBuilder::build_scalar_vtnodes_for_non_packed_nodes() {
for (int i = 0; i < _vloop_analyzer.body().body().length(); i++) {
Node* n = _vloop_analyzer.body().body().at(i);
if (_packset.get_pack(n) != nullptr) { continue; }
VTransformScalarNode* vtn = new (_vtransform.arena()) VTransformScalarNode(_vtransform, n);
map_node_to_vtnode(n, vtn);
}
}
void SuperWordVTransformBuilder::build_inputs_for_vector_vtnodes(VectorSet& vtn_memory_dependencies) {
for (int i = 0; i < _packset.length(); i++) {
Node_List* pack = _packset.at(i);
Node* p0 = pack->at(0);
VTransformVectorNode* vtn = get_vtnode(p0)->isa_Vector();
assert(vtn != nullptr, "all packs must have vector vtnodes");
vtn_memory_dependencies.clear(); // Add every memory dependency only once per vtn.
if (p0->is_Load()) {
set_req_with_scalar(p0, vtn, MemNode::Address);
for (uint k = 0; k < pack->size(); k++) {
add_memory_dependencies_of_node_to_vtnode(pack->at(k), vtn, vtn_memory_dependencies);
}
} else if (p0->is_Store()) {
set_req_with_scalar(p0, vtn, MemNode::Address);
set_req_with_vector(pack, vtn, MemNode::ValueIn);
for (uint k = 0; k < pack->size(); k++) {
add_memory_dependencies_of_node_to_vtnode(pack->at(k), vtn, vtn_memory_dependencies);
}
} else if (vtn->isa_ReductionVector() != nullptr) {
set_req_with_scalar(p0, vtn, 1); // scalar init
set_req_with_vector(pack, vtn, 2); // vector
} else {
assert(vtn->isa_ElementWiseVector() != nullptr, "all other vtnodes are handled above");
if (VectorNode::is_scalar_rotate(p0) &&
p0->in(2)->is_Con() &&
Matcher::supports_vector_constant_rotates(p0->in(2)->get_int())) {
set_req_with_vector(pack, vtn, 1);
set_req_with_scalar(p0, vtn, 2); // constant rotation
} else if (VectorNode::is_roundopD(p0)) {
set_req_with_vector(pack, vtn, 1);
set_req_with_scalar(p0, vtn, 2); // constant rounding mode
} else if (p0->is_CMove()) {
// Cmp + Bool + CMove -> VectorMaskCmp + VectorBlend.
set_all_req_with_vectors(pack, vtn);
VTransformBoolVectorNode* vtn_mask_cmp = vtn->in(1)->isa_BoolVector();
if (vtn_mask_cmp->test()._is_negated) {
vtn->swap_req(2, 3); // swap if test was negated.
}
} else {
set_all_req_with_vectors(pack, vtn);
}
}
}
}
void SuperWordVTransformBuilder::build_inputs_for_scalar_vtnodes(VectorSet& vtn_memory_dependencies) {
for (int i = 0; i < _vloop_analyzer.body().body().length(); i++) {
Node* n = _vloop_analyzer.body().body().at(i);
VTransformScalarNode* vtn = get_vtnode(n)->isa_Scalar();
if (vtn == nullptr) { continue; }
vtn_memory_dependencies.clear(); // Add every dependency only once per vtn.
if (n->is_Load()) {
set_req_with_scalar(n, vtn, MemNode::Address);
add_memory_dependencies_of_node_to_vtnode(n, vtn, vtn_memory_dependencies);
} else if (n->is_Store()) {
set_req_with_scalar(n, vtn, MemNode::Address);
set_req_with_scalar(n, vtn, MemNode::ValueIn);
add_memory_dependencies_of_node_to_vtnode(n, vtn, vtn_memory_dependencies);
} else if (n->is_CountedLoop()) {
continue; // Is "root", has no dependency.
} else if (n->is_Phi()) {
// CountedLoop Phi's: ignore backedge (and entry value).
assert(n->in(0) == _vloop.cl(), "only Phi's from the CountedLoop allowed");
set_req_with_scalar(n, vtn, 0);
continue;
} else {
set_all_req_with_scalars(n, vtn);
}
}
}
// Create a vtnode for each pack. No in/out edges set yet.
VTransformVectorNode* SuperWordVTransformBuilder::make_vector_vtnode_for_pack(const Node_List* pack) const {
uint pack_size = pack->size();
Node* p0 = pack->at(0);
int opc = p0->Opcode();
VTransformVectorNode* vtn = nullptr;
if (p0->is_Load()) {
const VPointer& scalar_p = _vloop_analyzer.vpointers().vpointer(p0->as_Load());
const VPointer vector_p(scalar_p.make_with_size(scalar_p.size() * pack_size));
vtn = new (_vtransform.arena()) VTransformLoadVectorNode(_vtransform, pack_size, vector_p);
} else if (p0->is_Store()) {
const VPointer& scalar_p = _vloop_analyzer.vpointers().vpointer(p0->as_Store());
const VPointer vector_p(scalar_p.make_with_size(scalar_p.size() * pack_size));
vtn = new (_vtransform.arena()) VTransformStoreVectorNode(_vtransform, pack_size, vector_p);
} else if (p0->is_Bool()) {
VTransformBoolTest kind = _packset.get_bool_test(pack);
vtn = new (_vtransform.arena()) VTransformBoolVectorNode(_vtransform, pack_size, kind);
} else if (_vloop_analyzer.reductions().is_marked_reduction(p0)) {
vtn = new (_vtransform.arena()) VTransformReductionVectorNode(_vtransform, pack_size);
} else if (VectorNode::is_muladds2i(p0)) {
// A special kind of binary element-wise vector op: the inputs are "ints" a and b,
// but reinterpreted as two "shorts" [a0, a1] and [b0, b1]:
// v = MulAddS2I(a, b) = a0 * b0 + a1 + b1
assert(p0->req() == 5, "MulAddS2I should have 4 operands");
vtn = new (_vtransform.arena()) VTransformElementWiseVectorNode(_vtransform, 3, pack_size);
} else {
assert(p0->req() == 3 ||
p0->is_CMove() ||
VectorNode::is_scalar_op_that_returns_int_but_vector_op_returns_long(opc) ||
VectorNode::is_convert_opcode(opc) ||
VectorNode::is_reinterpret_opcode(opc) ||
VectorNode::is_scalar_unary_op_with_equal_input_and_output_types(opc) ||
opc == Op_FmaD ||
opc == Op_FmaF ||
opc == Op_FmaHF ||
opc == Op_SignumF ||
opc == Op_SignumD,
"pack type must be in this list");
vtn = new (_vtransform.arena()) VTransformElementWiseVectorNode(_vtransform, p0->req(), pack_size);
}
vtn->set_nodes(pack);
return vtn;
}
void SuperWordVTransformBuilder::set_req_with_scalar(Node* n, VTransformNode* vtn, const int index) {
VTransformNode* req = get_vtnode_or_wrap_as_input_scalar(n->in(index));
vtn->set_req(index, req);
}
// Either get the existing vtnode vector input (when input is a pack), or else make a
// new vector vtnode for the input (e.g. for Replicate or PopulateIndex).
VTransformNode* SuperWordVTransformBuilder::get_or_make_vtnode_vector_input_at_index(const Node_List* pack, const int index) {
Node* p0 = pack->at(0);
Node_List* pack_in = _packset.pack_input_at_index_or_null(pack, index);
if (pack_in != nullptr) {
// Input is a matching pack -> vtnode already exists.
assert(index != 2 || !VectorNode::is_shift(p0), "shift's count cannot be vector");
return get_vtnode(pack_in->at(0));
}
if (VectorNode::is_muladds2i(p0)) {
assert(_packset.is_muladds2i_pack_with_pack_inputs(pack), "inputs must all be packs");
// All inputs are strided (stride = 2), either with offset 0 or 1.
Node_List* pack_in0 = _packset.strided_pack_input_at_index_or_null(pack, index, 2, 0);
if (pack_in0 != nullptr) {
return get_vtnode(pack_in0->at(0));
}
Node_List* pack_in1 = _packset.strided_pack_input_at_index_or_null(pack, index, 2, 1);
if (pack_in1 != nullptr) {
return get_vtnode(pack_in1->at(0));
}
}
Node* same_input = _packset.same_inputs_at_index_or_null(pack, index);
if (same_input == nullptr && p0->in(index) == _vloop.iv()) {
// PopulateIndex: [iv+0, iv+1, iv+2, ...]
VTransformNode* iv_vtn = get_vtnode_or_wrap_as_input_scalar(_vloop.iv());
BasicType p0_bt = _vloop_analyzer.types().velt_basic_type(p0);
// If we have subword type, take that type directly. If p0 is some ConvI2L/F/D,
// then the p0_bt can also be L/F/D but we need to produce ints for the input of
// the ConvI2L/F/D.
BasicType element_bt = is_subword_type(p0_bt) ? p0_bt : T_INT;
VTransformNode* populate_index = new (_vtransform.arena()) VTransformPopulateIndexNode(_vtransform, pack->size(), element_bt);
populate_index->set_req(1, iv_vtn);
return populate_index;
}
if (same_input != nullptr) {
VTransformNode* same_input_vtn = get_vtnode_or_wrap_as_input_scalar(same_input);
if (index == 2 && VectorNode::is_shift(p0)) {
// Scalar shift count for vector shift operation: vec2 = shiftV(vec1, scalar_count)
// Scalar shift operations masks the shift count, but the vector shift does not, so
// create a special ShiftCount node.
BasicType element_bt = _vloop_analyzer.types().velt_basic_type(p0);
juint mask = (p0->bottom_type() == TypeInt::INT) ? (BitsPerInt - 1) : (BitsPerLong - 1);
VTransformNode* shift_count = new (_vtransform.arena()) VTransformShiftCountNode(_vtransform, pack->size(), element_bt, mask, p0->Opcode());
shift_count->set_req(1, same_input_vtn);
return shift_count;
} else {
// Replicate the scalar same_input to every vector element.
// In some rare case, p0 is Convert node such as a ConvL2I: all
// ConvL2I nodes in the pack only differ in their types.
// velt_basic_type(p0) is the output type of the pack. In the
// case of a ConvL2I, it can be int or some narrower type such
// as short etc. But given we replicate the input of the Convert
// node, we have to use the input type instead.
BasicType element_type = p0->is_Convert() ? p0->in(1)->bottom_type()->basic_type() : _vloop_analyzer.types().velt_basic_type(p0);
if (index == 2 && VectorNode::is_scalar_rotate(p0) && element_type == T_LONG) {
// Scalar rotate has int rotation value, but the scalar rotate expects longs.
assert(same_input->bottom_type()->isa_int(), "scalar rotate expects int rotation");
VTransformNode* conv = new (_vtransform.arena()) VTransformConvI2LNode(_vtransform);
conv->set_req(1, same_input_vtn);
same_input_vtn = conv;
}
VTransformNode* replicate = new (_vtransform.arena()) VTransformReplicateNode(_vtransform, pack->size(), element_type);
replicate->set_req(1, same_input_vtn);
return replicate;
}
}
// The input is neither a pack not a same_input node. SuperWord::profitable does not allow
// any other case. In the future, we could insert a PackNode.
#ifdef ASSERT
tty->print_cr("\nSuperWordVTransformBuilder::get_or_make_vtnode_vector_input_at_index: index=%d", index);
pack->dump();
assert(false, "Pack input was neither a pack nor a same_input node");
#endif
ShouldNotReachHere();
}
VTransformNode* SuperWordVTransformBuilder::get_vtnode_or_wrap_as_input_scalar(Node* n) {
VTransformNode* vtn = get_vtnode_or_null(n);
if (vtn != nullptr) { return vtn; }
assert(!_vloop.in_bb(n), "only nodes outside the loop can be input nodes to the loop");
vtn = new (_vtransform.arena()) VTransformInputScalarNode(_vtransform, n);
map_node_to_vtnode(n, vtn);
return vtn;
}
void SuperWordVTransformBuilder::set_req_with_vector(const Node_List* pack, VTransformNode* vtn, int j) {
VTransformNode* req = get_or_make_vtnode_vector_input_at_index(pack, j);
vtn->set_req(j, req);
}
void SuperWordVTransformBuilder::set_all_req_with_scalars(Node* n, VTransformNode* vtn) {
assert(vtn->req() == n->req(), "scalars must have same number of reqs");
for (uint j = 0; j < n->req(); j++) {
Node* def = n->in(j);
if (def == nullptr) { continue; }
set_req_with_scalar(n, vtn, j);
}
}
void SuperWordVTransformBuilder::set_all_req_with_vectors(const Node_List* pack, VTransformNode* vtn) {
Node* p0 = pack->at(0);
assert(vtn->req() <= p0->req(), "must have at at most as many reqs");
// Vectors have no ctrl, so ignore it.
for (uint j = 1; j < vtn->req(); j++) {
Node* def = p0->in(j);
if (def == nullptr) { continue; }
set_req_with_vector(pack, vtn, j);
}
}
void SuperWordVTransformBuilder::add_memory_dependencies_of_node_to_vtnode(Node*n, VTransformNode* vtn, VectorSet& vtn_memory_dependencies) {
for (VLoopDependencyGraph::PredsIterator preds(_vloop_analyzer.dependency_graph(), n); !preds.done(); preds.next()) {
Node* pred = preds.current();
if (!_vloop.in_bb(pred)) { continue; }
if (!preds.is_current_memory_edge()) { continue; }
// Only track every memory edge once.
VTransformNode* dependency = get_vtnode(pred);
if (vtn_memory_dependencies.test_set(dependency->_idx)) { continue; }
assert(n->is_Mem() && pred->is_Mem(), "only memory edges");
vtn->add_memory_dependency(dependency); // Add every dependency only once per vtn.
}
}