blob: 555f565360d2e7deb5d027f83ee2ae83a9411f55 [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.
*/
#ifndef SHARE_OPTO_VTRANSFORM_HPP
#define SHARE_OPTO_VTRANSFORM_HPP
#include "opto/node.hpp"
#include "opto/vectorization.hpp"
// VTransform:
// - Models the transformation of the scalar loop to vectorized loop:
// It is a "C2 subgraph" -> "C2 subgraph" mapping.
// - The VTransform contains a graph (VTransformGraph), which consists of
// many vtnodes (VTransformNode).
// - Each vtnode models a part of the transformation, and is supposed
// to represent the output C2 nodes after the vectorization as closely
// as possible.
//
// This is the life-cycle of a VTransform:
// - Construction:
// - From SuperWord, with the SuperWordVTransformBuilder.
//
// - Future Plans: optimize, if-conversion, etc.
//
// - Schedule:
// - Compute linearization of the VTransformGraph, into an order that respects
// all edges in the graph (bailout if cycle detected).
//
// - Apply:
// - Changes to the C2 IR are only made once the "apply" method is called.
// - Each vtnode generates its corresponding scalar and vector C2 nodes,
// possibly replacing old scalar C2 nodes.
//
// Future Plans with VTransform:
// - Cost model: estimate if vectorization is profitable.
// - Optimizations: moving unordered reductions out of the loop, whih decreases cost.
// - Pack/Unpack/Shuffle: introduce additional nodes not present in the scalar loop.
// This is difficult to do with the SuperWord packset approach.
// - If-conversion: convert predicated nodes into CFG.
typedef int VTransformNodeIDX;
class VTransformNode;
class VTransformScalarNode;
class VTransformInputScalarNode;
class VTransformVectorNode;
class VTransformElementWiseVectorNode;
class VTransformBoolVectorNode;
class VTransformReductionVectorNode;
class VTransformMemVectorNode;
class VTransformLoadVectorNode;
class VTransformStoreVectorNode;
// Result from VTransformNode::apply
class VTransformApplyResult {
private:
Node* const _node;
const uint _vector_length; // number of elements
const uint _vector_width; // total width in bytes
VTransformApplyResult(Node* n, uint vector_length, uint vector_width) :
_node(n),
_vector_length(vector_length),
_vector_width(vector_width) {}
public:
static VTransformApplyResult make_scalar(Node* n) {
return VTransformApplyResult(n, 0, 0);
}
static VTransformApplyResult make_vector(Node* n, uint vector_length, uint vector_width) {
assert(vector_length > 0 && vector_width > 0, "must have nonzero size");
return VTransformApplyResult(n, vector_length, vector_width);
}
static VTransformApplyResult make_empty() {
return VTransformApplyResult(nullptr, 0, 0);
}
Node* node() const { return _node; }
uint vector_length() const { return _vector_length; }
uint vector_width() const { return _vector_width; }
NOT_PRODUCT( void trace(VTransformNode* vtnode) const; )
};
#ifndef PRODUCT
// Convenience class for tracing flags.
class VTransformTrace {
public:
const bool _verbose;
const bool _rejections;
const bool _align_vector;
const bool _speculative_runtime_checks;
const bool _info;
VTransformTrace(const VTrace& vtrace,
const bool is_trace_rejections,
const bool is_trace_align_vector,
const bool is_trace_speculative_runtime_checks,
const bool is_trace_info) :
_verbose (vtrace.is_trace(TraceAutoVectorizationTag::ALL)),
_rejections (_verbose | is_trace_vtransform(vtrace) | is_trace_rejections),
_align_vector (_verbose | is_trace_vtransform(vtrace) | is_trace_align_vector),
_speculative_runtime_checks(_verbose | is_trace_vtransform(vtrace) | is_trace_speculative_runtime_checks),
_info (_verbose | is_trace_vtransform(vtrace) | is_trace_info) {}
static bool is_trace_vtransform(const VTrace& vtrace) {
return vtrace.is_trace(TraceAutoVectorizationTag::VTRANSFORM);
}
};
#endif
// VTransformGraph: component of VTransform
// See description at top of this file.
class VTransformGraph : public StackObj {
private:
const VLoopAnalyzer& _vloop_analyzer;
const VLoop& _vloop;
NOT_PRODUCT(const VTransformTrace _trace;)
VTransformNodeIDX _next_idx;
GrowableArray<VTransformNode*> _vtnodes;
// Schedule (linearization) of the graph. We use this to reorder the memory graph
// before inserting vector operations.
GrowableArray<VTransformNode*> _schedule;
public:
VTransformGraph(const VLoopAnalyzer& vloop_analyzer,
Arena& arena
NOT_PRODUCT( COMMA const VTransformTrace trace)) :
_vloop_analyzer(vloop_analyzer),
_vloop(vloop_analyzer.vloop()),
NOT_PRODUCT(_trace(trace) COMMA)
_next_idx(0),
_vtnodes(&arena, _vloop.estimated_body_length(), 0, nullptr),
_schedule(&arena, _vloop.estimated_body_length(), 0, nullptr) {}
VTransformNodeIDX new_idx() { return _next_idx++; }
void add_vtnode(VTransformNode* vtnode);
DEBUG_ONLY( bool is_empty() const { return _vtnodes.is_empty(); } )
DEBUG_ONLY( bool is_scheduled() const { return _schedule.is_nonempty(); } )
const GrowableArray<VTransformNode*>& vtnodes() const { return _vtnodes; }
bool schedule();
bool has_store_to_load_forwarding_failure(const VLoopAnalyzer& vloop_analyzer) const;
void apply_memops_reordering_with_schedule() const;
void apply_vectorization_for_each_vtnode(uint& max_vector_length, uint& max_vector_width) const;
private:
// VLoop accessors
PhaseIdealLoop* phase() const { return _vloop.phase(); }
PhaseIterGVN& igvn() const { return _vloop.phase()->igvn(); }
bool in_bb(const Node* n) const { return _vloop.in_bb(n); }
void collect_nodes_without_req_or_dependency(GrowableArray<VTransformNode*>& stack) const;
template<typename Callback>
void for_each_memop_in_schedule(Callback callback) const;
#ifndef PRODUCT
void print_vtnodes() const;
void print_schedule() const;
void print_memops_schedule() const;
void trace_schedule_cycle(const GrowableArray<VTransformNode*>& stack,
const VectorSet& pre_visited,
const VectorSet& post_visited) const;
#endif
};
// VTransform: models the transformation of the scalar loop to vectorized loop.
// It is a "C2 subgraph" to "C2 subgraph" mapping.
// See description at top of this file.
class VTransform : public StackObj {
private:
const VLoopAnalyzer& _vloop_analyzer;
const VLoop& _vloop;
NOT_PRODUCT(const VTransformTrace _trace;)
// Everything in the vtransform is allocated from this arena, including all vtnodes.
Arena _arena;
VTransformGraph _graph;
// Memory reference, and the alignment width (aw) for which we align the main-loop,
// by adjusting the pre-loop limit.
MemNode const* _mem_ref_for_main_loop_alignment;
int _aw_for_main_loop_alignment;
public:
VTransform(const VLoopAnalyzer& vloop_analyzer,
MemNode const* mem_ref_for_main_loop_alignment,
int aw_for_main_loop_alignment
NOT_PRODUCT( COMMA const VTransformTrace trace)
) :
_vloop_analyzer(vloop_analyzer),
_vloop(vloop_analyzer.vloop()),
NOT_PRODUCT(_trace(trace) COMMA)
_arena(mtCompiler, Arena::Tag::tag_superword),
_graph(_vloop_analyzer, _arena NOT_PRODUCT(COMMA _trace)),
_mem_ref_for_main_loop_alignment(mem_ref_for_main_loop_alignment),
_aw_for_main_loop_alignment(aw_for_main_loop_alignment) {}
const VLoopAnalyzer& vloop_analyzer() const { return _vloop_analyzer; }
Arena* arena() { return &_arena; }
DEBUG_ONLY( bool has_graph() const { return !_graph.is_empty(); } )
VTransformGraph& graph() { return _graph; }
bool schedule() { return _graph.schedule(); }
bool has_store_to_load_forwarding_failure() const { return _graph.has_store_to_load_forwarding_failure(_vloop_analyzer); }
void apply();
private:
// VLoop accessors
PhaseIdealLoop* phase() const { return _vloop.phase(); }
PhaseIterGVN& igvn() const { return _vloop.phase()->igvn(); }
IdealLoopTree* lpt() const { return _vloop.lpt(); }
CountedLoopNode* cl() const { return _vloop.cl(); }
int iv_stride() const { return cl()->stride_con(); }
// VLoopVPointers accessors
const VPointer& vpointer(const MemNode* mem) const {
return _vloop_analyzer.vpointers().vpointer(mem);
}
// Ensure that the main loop vectors are aligned by adjusting the pre loop limit.
void determine_mem_ref_and_aw_for_main_loop_alignment();
void adjust_pre_loop_limit_to_align_main_loop_vectors();
void apply_speculative_runtime_checks();
void add_speculative_alignment_check(Node* node, juint alignment);
void add_speculative_check(BoolNode* bol);
void apply_vectorization() const;
};
// The vtnodes (VTransformNode) resemble the C2 IR Nodes, and model a part of the
// VTransform. Many such vtnodes make up the VTransformGraph. The vtnodes represent
// the resulting scalar and vector nodes as closely as possible.
// See description at top of this file.
class VTransformNode : public ArenaObj {
public:
const VTransformNodeIDX _idx;
private:
// _in is split into required inputs (_req, i.e. all data dependencies),
// and memory dependencies.
const uint _req;
GrowableArray<VTransformNode*> _in;
GrowableArray<VTransformNode*> _out;
public:
VTransformNode(VTransform& vtransform, const uint req) :
_idx(vtransform.graph().new_idx()),
_req(req),
_in(vtransform.arena(), req, req, nullptr),
_out(vtransform.arena(), 4, 0, nullptr)
{
vtransform.graph().add_vtnode(this);
}
void set_req(uint i, VTransformNode* n) {
assert(i < _req, "must be a req");
assert(_in.at(i) == nullptr && n != nullptr, "only set once");
_in.at_put(i, n);
n->add_out(this);
}
void swap_req(uint i, uint j) {
assert(i < _req, "must be a req");
assert(j < _req, "must be a req");
VTransformNode* tmp = _in.at(i);
_in.at_put(i, _in.at(j));
_in.at_put(j, tmp);
}
void add_memory_dependency(VTransformNode* n) {
assert(n != nullptr, "no need to add nullptr");
_in.push(n);
n->add_out(this);
}
void add_out(VTransformNode* n) {
_out.push(n);
}
uint req() const { return _req; }
VTransformNode* in(int i) const { return _in.at(i); }
int outs() const { return _out.length(); }
VTransformNode* out(int i) const { return _out.at(i); }
bool has_req_or_dependency() const {
for (int i = 0; i < _in.length(); i++) {
if (_in.at(i) != nullptr) { return true; }
}
return false;
}
virtual VTransformScalarNode* isa_Scalar() { return nullptr; }
virtual VTransformInputScalarNode* isa_InputScalar() { return nullptr; }
virtual VTransformVectorNode* isa_Vector() { return nullptr; }
virtual VTransformElementWiseVectorNode* isa_ElementWiseVector() { return nullptr; }
virtual VTransformBoolVectorNode* isa_BoolVector() { return nullptr; }
virtual VTransformReductionVectorNode* isa_ReductionVector() { return nullptr; }
virtual VTransformMemVectorNode* isa_MemVector() { return nullptr; }
virtual VTransformLoadVectorNode* isa_LoadVector() { return nullptr; }
virtual VTransformStoreVectorNode* isa_StoreVector() { return nullptr; }
virtual bool is_load_in_loop() const { return false; }
virtual bool is_load_or_store_in_loop() const { return false; }
virtual const VPointer& vpointer(const VLoopAnalyzer& vloop_analyzer) const { ShouldNotReachHere(); }
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const = 0;
Node* find_transformed_input(int i, const GrowableArray<Node*>& vnode_idx_to_transformed_node) const;
void register_new_node_from_vectorization(const VLoopAnalyzer& vloop_analyzer, Node* vn, Node* old_node) const;
NOT_PRODUCT(virtual const char* name() const = 0;)
NOT_PRODUCT(void print() const;)
NOT_PRODUCT(virtual void print_spec() const {};)
NOT_PRODUCT(static void print_node_idx(const VTransformNode* vtn);)
};
// Identity transform for scalar nodes.
class VTransformScalarNode : public VTransformNode {
private:
Node* _node;
public:
VTransformScalarNode(VTransform& vtransform, Node* n) :
VTransformNode(vtransform, n->req()), _node(n) {}
Node* node() const { return _node; }
virtual VTransformScalarNode* isa_Scalar() override { return this; }
virtual bool is_load_in_loop() const override { return _node->is_Load(); }
virtual bool is_load_or_store_in_loop() const override { return _node->is_Load() || _node->is_Store(); }
virtual const VPointer& vpointer(const VLoopAnalyzer& vloop_analyzer) const override { return vloop_analyzer.vpointers().vpointer(node()->as_Mem()); }
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "Scalar"; };)
NOT_PRODUCT(virtual void print_spec() const override;)
};
// Wrapper node for nodes outside the loop that are inputs to nodes in the loop.
// Since we want the loop-internal nodes to be able to reference all inputs as vtnodes,
// we must wrap the inputs that are outside the loop into special vtnodes, too.
class VTransformInputScalarNode : public VTransformScalarNode {
public:
VTransformInputScalarNode(VTransform& vtransform, Node* n) :
VTransformScalarNode(vtransform, n) {}
virtual VTransformInputScalarNode* isa_InputScalar() override { return this; }
virtual bool is_load_in_loop() const override { return false; }
virtual bool is_load_or_store_in_loop() const override { return false; }
NOT_PRODUCT(virtual const char* name() const override { return "InputScalar"; };)
};
// Transform produces a ReplicateNode, replicating the input to all vector lanes.
class VTransformReplicateNode : public VTransformNode {
private:
int _vlen;
BasicType _element_type;
public:
VTransformReplicateNode(VTransform& vtransform, int vlen, BasicType element_type) :
VTransformNode(vtransform, 2), _vlen(vlen), _element_type(element_type) {}
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "Replicate"; };)
NOT_PRODUCT(virtual void print_spec() const override;)
};
// Transform introduces a scalar ConvI2LNode that was not previously in the C2 graph.
class VTransformConvI2LNode : public VTransformNode {
public:
VTransformConvI2LNode(VTransform& vtransform) : VTransformNode(vtransform, 2) {}
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "ConvI2L"; };)
};
// Transform introduces a shift-count node that truncates the shift count for a vector shift.
class VTransformShiftCountNode : public VTransformNode {
private:
int _vlen;
const BasicType _element_bt;
juint _mask;
int _shift_opcode;
public:
VTransformShiftCountNode(VTransform& vtransform, int vlen, BasicType element_bt, juint mask, int shift_opcode) :
VTransformNode(vtransform, 2), _vlen(vlen), _element_bt(element_bt), _mask(mask), _shift_opcode(shift_opcode) {}
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "ShiftCount"; };)
NOT_PRODUCT(virtual void print_spec() const override;)
};
// Transform introduces a PopulateIndex node: [phi, phi+1, phi+2, phi+3, ...].
class VTransformPopulateIndexNode : public VTransformNode {
private:
int _vlen;
const BasicType _element_bt;
public:
VTransformPopulateIndexNode(VTransform& vtransform, int vlen, const BasicType element_bt) :
VTransformNode(vtransform, 2), _vlen(vlen), _element_bt(element_bt) {}
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "PopulateIndex"; };)
NOT_PRODUCT(virtual void print_spec() const override;)
};
// Base class for all vector vtnodes.
class VTransformVectorNode : public VTransformNode {
private:
GrowableArray<Node*> _nodes;
public:
VTransformVectorNode(VTransform& vtransform, const uint req, const uint number_of_nodes) :
VTransformNode(vtransform, req), _nodes(vtransform.arena(), number_of_nodes, number_of_nodes, nullptr) {}
void set_nodes(const Node_List* pack) {
for (uint k = 0; k < pack->size(); k++) {
_nodes.at_put(k, pack->at(k));
}
}
const GrowableArray<Node*>& nodes() const { return _nodes; }
virtual VTransformVectorNode* isa_Vector() override { return this; }
void register_new_node_from_vectorization_and_replace_scalar_nodes(const VLoopAnalyzer& vloop_analyzer, Node* vn) const;
NOT_PRODUCT(virtual void print_spec() const override;)
};
// Catch all for all element-wise vector operations.
class VTransformElementWiseVectorNode : public VTransformVectorNode {
public:
VTransformElementWiseVectorNode(VTransform& vtransform, uint req, uint number_of_nodes) :
VTransformVectorNode(vtransform, req, number_of_nodes) {}
virtual VTransformElementWiseVectorNode* isa_ElementWiseVector() override { return this; }
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "ElementWiseVector"; };)
};
struct VTransformBoolTest {
const BoolTest::mask _mask;
const bool _is_negated;
VTransformBoolTest(const BoolTest::mask mask, bool is_negated) :
_mask(mask), _is_negated(is_negated) {}
};
class VTransformBoolVectorNode : public VTransformElementWiseVectorNode {
private:
const VTransformBoolTest _test;
public:
VTransformBoolVectorNode(VTransform& vtransform, uint number_of_nodes, VTransformBoolTest test) :
VTransformElementWiseVectorNode(vtransform, 2, number_of_nodes), _test(test) {}
VTransformBoolTest test() const { return _test; }
virtual VTransformBoolVectorNode* isa_BoolVector() override { return this; }
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "BoolVector"; };)
};
class VTransformReductionVectorNode : public VTransformVectorNode {
public:
// req = 3 -> [ctrl, scalar init, vector]
VTransformReductionVectorNode(VTransform& vtransform, uint number_of_nodes) :
VTransformVectorNode(vtransform, 3, number_of_nodes) {}
virtual VTransformReductionVectorNode* isa_ReductionVector() override { return this; }
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "ReductionVector"; };)
};
class VTransformMemVectorNode : public VTransformVectorNode {
private:
const VPointer _vpointer; // with size of the vector
public:
VTransformMemVectorNode(VTransform& vtransform, const uint req, uint number_of_nodes, const VPointer& vpointer) :
VTransformVectorNode(vtransform, req, number_of_nodes),
_vpointer(vpointer) {}
virtual VTransformMemVectorNode* isa_MemVector() override { return this; }
virtual bool is_load_or_store_in_loop() const override { return true; }
virtual const VPointer& vpointer(const VLoopAnalyzer& vloop_analyzer) const override { return _vpointer; }
};
class VTransformLoadVectorNode : public VTransformMemVectorNode {
public:
// req = 3 -> [ctrl, mem, adr]
VTransformLoadVectorNode(VTransform& vtransform, uint number_of_nodes, const VPointer& vpointer) :
VTransformMemVectorNode(vtransform, 3, number_of_nodes, vpointer) {}
LoadNode::ControlDependency control_dependency() const;
virtual VTransformLoadVectorNode* isa_LoadVector() override { return this; }
virtual bool is_load_in_loop() const override { return true; }
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "LoadVector"; };)
};
class VTransformStoreVectorNode : public VTransformMemVectorNode {
public:
// req = 4 -> [ctrl, mem, adr, val]
VTransformStoreVectorNode(VTransform& vtransform, uint number_of_nodes, const VPointer& vpointer) :
VTransformMemVectorNode(vtransform, 4, number_of_nodes, vpointer) {}
virtual VTransformStoreVectorNode* isa_StoreVector() override { return this; }
virtual bool is_load_in_loop() const override { return false; }
virtual VTransformApplyResult apply(const VLoopAnalyzer& vloop_analyzer,
const GrowableArray<Node*>& vnode_idx_to_transformed_node) const override;
NOT_PRODUCT(virtual const char* name() const override { return "StoreVector"; };)
};
// Invoke callback on all memops, in the order of the schedule.
template<typename Callback>
void VTransformGraph::for_each_memop_in_schedule(Callback callback) const {
assert(_schedule.length() == _vtnodes.length(), "schedule was computed");
for (int i = 0; i < _schedule.length(); i++) {
VTransformNode* vtn = _schedule.at(i);
// We can ignore input nodes, they are outside the loop.
if (vtn->isa_InputScalar() != nullptr) { continue; }
VTransformScalarNode* scalar = vtn->isa_Scalar();
if (scalar != nullptr && scalar->node()->is_Mem()) {
callback(scalar->node()->as_Mem());
}
VTransformVectorNode* vector = vtn->isa_Vector();
if (vector != nullptr && vector->nodes().at(0)->is_Mem()) {
for (int j = 0; j < vector->nodes().length(); j++) {
callback(vector->nodes().at(j)->as_Mem());
}
}
}
}
#endif // SHARE_OPTO_VTRANSFORM_HPP