blob: 57a403b4498433dd81e2bdd84e5869867958a231 [file] [edit]
/*
* Copyright (c) 2007, 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_SUPERWORD_HPP
#define SHARE_OPTO_SUPERWORD_HPP
#include "opto/vectorization.hpp"
#include "opto/vtransform.hpp"
#include "utilities/growableArray.hpp"
//
// S U P E R W O R D T R A N S F O R M
//
// SuperWords are short, fixed length vectors.
//
// Algorithm from:
//
// Exploiting SuperWord Level Parallelism with
// Multimedia Instruction Sets
// by
// Samuel Larsen and Saman Amarasinghe
// MIT Laboratory for Computer Science
// date
// May 2000
// published in
// ACM SIGPLAN Notices
// Proceedings of ACM PLDI '00, Volume 35 Issue 5
//
// Definition 3.1 A Pack is an n-tuple, <s1, ...,sn>, where
// s1,...,sn are independent isomorphic statements in a basic
// block.
//
// Definition 3.2 A PackSet is a set of Packs.
//
// Definition 3.3 A Pair is a Pack of size two, where the
// first statement is considered the left element, and the
// second statement is considered the right element.
// The PairSet is a set of pairs. These are later combined to packs,
// and stored in the PackSet.
class PairSet : public StackObj {
private:
const VLoop& _vloop;
const VLoopBody& _body;
// Doubly-linked pairs. If not linked: -1
GrowableArray<int> _left_to_right; // bb_idx -> bb_idx
GrowableArray<int> _right_to_left; // bb_idx -> bb_idx
// Example:
//
// Pairs: (n1, n2) and (n2, n3)
// bb_idx(n1) = 1
// bb_idx(n2) = 3
// bb_idx(n3) = 5
//
// index / bb_idx: 0 1 2 3 4 5 6
//
// left_to_right: | | 3 | | 5 | | | |
// n1----->
// n2----->
//
// right_to_left: | | | | 1 | | 3 | |
// <------n2
// <------n3
//
// Nodes with bb_idx 0, 2, 4, and 6 are in no pair, they are thus neither left nor right elements,
// and hence have no entries in the mapping.
//
// Nodes with bb_idx 1 and 3 (n1 and n2) are both a left element in some pair. Therefore, they both
// have an entry in the left_to_right mapping. This mapping indicates which right element they are
// paired with, namely the nodes with bb_idx 3 and 5 (n2 and n3), respectively.
//
// Nodes with bb_idx 3 and 5 (n2 and n4) are both a right element in some pair. Therefore, they both
// have an entry in the right_to_left mapping. This mapping indicates which left element they are
// paired with, namely the nodes with bb_idx 1 and 3 (n1 and n2), respectively.
//
// Node n1 with bb_idx 1 is not a right element in any pair, thus its right_to_left is empty.
//
// Node n2 with bb_idx 3 is both a left element of pair (n2, n3), and a right element of pair (n1, n2).
// Thus it has entries in both left_to_right (mapping n2->n3) and right_to_left (mapping n2->n1).
//
// Node n3 with bb_idx 5 is not a left element in any pair, thus its left_to_right is empty.
// List of all left elements bb_idx, in the order of pair addition.
GrowableArray<int> _lefts_in_insertion_order;
public:
// Initialize empty, i.e. all not linked (-1).
PairSet(Arena* arena, const VLoopAnalyzer& vloop_analyzer) :
_vloop(vloop_analyzer.vloop()),
_body(vloop_analyzer.body()),
_left_to_right(arena, _body.body().length(), _body.body().length(), -1),
_right_to_left(arena, _body.body().length(), _body.body().length(), -1),
_lefts_in_insertion_order(arena, 8, 0, 0) {}
const VLoopBody& body() const { return _body; }
bool is_empty() const { return _lefts_in_insertion_order.is_empty(); }
bool is_left(int i) const { return _left_to_right.at(i) != -1; }
bool is_right(int i) const { return _right_to_left.at(i) != -1; }
bool is_left(const Node* n) const { return _vloop.in_bb(n) && is_left( _body.bb_idx(n)); }
bool is_right(const Node* n) const { return _vloop.in_bb(n) && is_right(_body.bb_idx(n)); }
bool is_pair(const Node* n1, const Node* n2) const { return is_left(n1) && get_right_for(n1) == n2; }
bool is_left_in_a_left_most_pair(int i) const { return is_left(i) && !is_right(i); }
bool is_right_in_a_right_most_pair(int i) const { return !is_left(i) && is_right(i); }
bool is_left_in_a_left_most_pair(const Node* n) const { return is_left_in_a_left_most_pair( _body.bb_idx(n)); }
bool is_right_in_a_right_most_pair(const Node* n) const { return is_right_in_a_right_most_pair(_body.bb_idx(n)); }
int get_right_for(int i) const { return _left_to_right.at(i); }
Node* get_right_for(const Node* n) const { return _body.body().at(get_right_for(_body.bb_idx(n))); }
Node* get_right_or_null_for(const Node* n) const { return is_left(n) ? get_right_for(n) : nullptr; }
// To access elements in insertion order:
int length() const { return _lefts_in_insertion_order.length(); }
Node* left_at_in_insertion_order(int i) const { return _body.body().at(_lefts_in_insertion_order.at(i)); }
Node* right_at_in_insertion_order(int i) const { return _body.body().at(get_right_for(_lefts_in_insertion_order.at(i))); }
void add_pair(Node* n1, Node* n2) {
assert(n1 != nullptr && n2 != nullptr && n1 != n2, "no nullptr, and different nodes");
assert(!is_left(n1) && !is_right(n2), "cannot be left twice, or right twice");
int bb_idx_1 = _body.bb_idx(n1);
int bb_idx_2 = _body.bb_idx(n2);
_left_to_right.at_put(bb_idx_1, bb_idx_2);
_right_to_left.at_put(bb_idx_2, bb_idx_1);
_lefts_in_insertion_order.append(bb_idx_1);
assert(is_left(n1) && is_right(n2), "must be set now");
}
NOT_PRODUCT(void print() const;)
};
// Iterate over the PairSet, pair-chain by pair-chain.
// A pair-chain starts with a "left-most" pair (n1, n2), where n1 is never a right-element
// in any pair. We walk a chain: (n2, n3), (n3, n4) ... until we hit a "right-most" pair
// where the right-element is never a left-element of any pair.
// These pair-chains will later be combined into packs by combine_pairs_to_longer_packs.
class PairSetIterator : public StackObj {
private:
const PairSet& _pairset;
const VLoopBody& _body;
int _chain_start_bb_idx; // bb_idx of left-element in the left-most pair.
int _current_bb_idx; // bb_idx of left-element of the current pair.
const int _end_bb_idx;
public:
PairSetIterator(const PairSet& pairset) :
_pairset(pairset),
_body(pairset.body()),
_chain_start_bb_idx(-1),
_current_bb_idx(-1),
_end_bb_idx(_body.body().length())
{
next_chain();
}
bool done() const {
return _chain_start_bb_idx >= _end_bb_idx;
}
Node* left() const {
return _body.body().at(_current_bb_idx);
}
Node* right() const {
int bb_idx_2 = _pairset.get_right_for(_current_bb_idx);
return _body.body().at(bb_idx_2);
}
// Try to keep walking on the current pair-chain, else find a new pair-chain.
void next() {
assert(_pairset.is_left(_current_bb_idx), "current was valid");
_current_bb_idx = _pairset.get_right_for(_current_bb_idx);
if (!_pairset.is_left(_current_bb_idx)) {
next_chain();
}
}
private:
void next_chain() {
do {
_chain_start_bb_idx++;
} while (!done() && !_pairset.is_left_in_a_left_most_pair(_chain_start_bb_idx));
_current_bb_idx = _chain_start_bb_idx;
}
};
class SplitTask {
private:
enum Kind {
// The lambda method for split_packs can return one of these tasks:
Unchanged, // The pack is left in the packset, unchanged.
Rejected, // The pack is removed from the packset.
Split, // Split away split_size nodes from the end of the pack.
};
const Kind _kind;
const uint _split_size;
const char* _message;
SplitTask(const Kind kind, const uint split_size, const char* message) :
_kind(kind), _split_size(split_size), _message(message)
{
assert(message != nullptr, "must have message");
assert(_kind != Unchanged || split_size == 0, "unchanged task conditions");
assert(_kind != Rejected || split_size == 0, "reject task conditions");
assert(_kind != Split || split_size != 0, "split task conditions");
}
public:
static SplitTask make_split(const uint split_size, const char* message) {
return SplitTask(Split, split_size, message);
}
static SplitTask make_unchanged() {
return SplitTask(Unchanged, 0, "unchanged");
}
static SplitTask make_rejected(const char* message) {
return SplitTask(Rejected, 0, message);
}
bool is_unchanged() const { return _kind == Unchanged; }
bool is_rejected() const { return _kind == Rejected; }
bool is_split() const { return _kind == Split; }
const char* message() const { return _message; }
uint split_size() const {
assert(is_split(), "only split tasks have split_size");
return _split_size;
}
};
class SplitStatus {
private:
enum Kind {
// After split_pack, we have: first_pack second_pack
Unchanged, // The pack is left in the pack, unchanged. old_pack nullptr
Rejected, // The pack is removed from the packset. nullptr nullptr
Modified, // The pack had some nodes removed. old_pack nullptr
Split, // The pack was split into two packs. pack1 pack2
};
Kind _kind;
Node_List* _first_pack;
Node_List* _second_pack;
SplitStatus(Kind kind, Node_List* first_pack, Node_List* second_pack) :
_kind(kind), _first_pack(first_pack), _second_pack(second_pack)
{
assert(_kind != Unchanged || (first_pack != nullptr && second_pack == nullptr), "unchanged status conditions");
assert(_kind != Rejected || (first_pack == nullptr && second_pack == nullptr), "rejected status conditions");
assert(_kind != Modified || (first_pack != nullptr && second_pack == nullptr), "modified status conditions");
assert(_kind != Split || (first_pack != nullptr && second_pack != nullptr), "split status conditions");
}
public:
static SplitStatus make_unchanged(Node_List* old_pack) {
return SplitStatus(Unchanged, old_pack, nullptr);
}
static SplitStatus make_rejected() {
return SplitStatus(Rejected, nullptr, nullptr);
}
static SplitStatus make_modified(Node_List* first_pack) {
return SplitStatus(Modified, first_pack, nullptr);
}
static SplitStatus make_split(Node_List* first_pack, Node_List* second_pack) {
return SplitStatus(Split, first_pack, second_pack);
}
bool is_unchanged() const { return _kind == Unchanged; }
Node_List* first_pack() const { return _first_pack; }
Node_List* second_pack() const { return _second_pack; }
};
class PackSet : public StackObj {
private:
const VLoop& _vloop;
const VLoopBody& _body;
// Set of all packs:
GrowableArray<Node_List*> _packs;
// Mapping from nodes to their pack: bb_idx -> pack
GrowableArray<Node_List*> _node_to_pack;
NOT_PRODUCT(const bool _trace_packset;)
NOT_PRODUCT(const bool _trace_rejections;)
public:
// Initialize empty, i.e. no packs, and unmapped (nullptr).
PackSet(Arena* arena, const VLoopAnalyzer& vloop_analyzer
NOT_PRODUCT(COMMA bool trace_packset COMMA bool trace_rejections)
) :
_vloop(vloop_analyzer.vloop()),
_body(vloop_analyzer.body()),
_packs(arena, 8, 0, nullptr),
_node_to_pack(arena, _body.body().length(), _body.body().length(), nullptr)
NOT_PRODUCT(COMMA _trace_packset(trace_packset))
NOT_PRODUCT(COMMA _trace_rejections(trace_rejections))
{}
// Accessors to iterate over packs.
int length() const { return _packs.length(); }
bool is_empty() const { return _packs.is_empty(); }
Node_List* at(int i) const { return _packs.at(i); }
private:
void map_node_in_pack(const Node* n, Node_List* new_pack) {
assert(get_pack(n) == nullptr, "was previously unmapped");
_node_to_pack.at_put(_body.bb_idx(n), new_pack);
}
void remap_node_in_pack(const Node* n, Node_List* new_pack) {
assert(get_pack(n) != nullptr && new_pack != nullptr && get_pack(n) != new_pack, "was previously mapped");
_node_to_pack.at_put(_body.bb_idx(n), new_pack);
}
void unmap_node_in_pack(const Node* n) {
assert(get_pack(n) != nullptr, "was previously mapped");
_node_to_pack.at_put(_body.bb_idx(n), nullptr);
}
void unmap_all_nodes_in_pack(Node_List* old_pack) {
for (uint i = 0; i < old_pack->size(); i++) {
unmap_node_in_pack(old_pack->at(i));
}
}
public:
Node_List* get_pack(const Node* n) const { return !_vloop.in_bb(n) ? nullptr : _node_to_pack.at(_body.bb_idx(n)); }
void add_pack(Node_List* pack) {
_packs.append(pack);
for (uint i = 0; i < pack->size(); i++) {
Node* n = pack->at(i);
map_node_in_pack(n, pack);
}
}
Node_List* strided_pack_input_at_index_or_null(const Node_List* pack, const int index, const int stride, const int offset) const;
bool is_muladds2i_pack_with_pack_inputs(const Node_List* pack) const;
Node* same_inputs_at_index_or_null(const Node_List* pack, const int index) const;
VTransformBoolTest get_bool_test(const Node_List* bool_pack) const;
Node_List* pack_input_at_index_or_null(const Node_List* pack, const int index) const {
return strided_pack_input_at_index_or_null(pack, index, 1, 0);
}
private:
SplitStatus split_pack(const char* split_name, Node_List* pack, SplitTask task);
public:
template <typename SplitStrategy>
void split_packs(const char* split_name, SplitStrategy strategy);
template <typename FilterPredicate>
void filter_packs(const char* filter_name,
const char* rejection_message,
FilterPredicate filter);
void clear() { _packs.clear(); }
private:
NOT_PRODUCT(bool is_trace_superword_packset() const { return _trace_packset; })
NOT_PRODUCT(bool is_trace_superword_rejections() const { return _trace_rejections; })
public:
DEBUG_ONLY(void verify() const;)
NOT_PRODUCT(void print() const;)
NOT_PRODUCT(static void print_pack(Node_List* pack);)
};
// -----------------------------SuperWord---------------------------------
// Transforms scalar operations into packed (superword) operations.
class SuperWord : public ResourceObj {
private:
const VLoopAnalyzer& _vloop_analyzer;
const VLoop& _vloop;
// Arena for small data structures. Large data structures are allocated in
// VSharedData, and reused over many AutoVectorizations.
Arena _arena;
CloneMap& _clone_map; // map of nodes created in cloning
PairSet _pairset;
PackSet _packset;
// 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:
SuperWord(const VLoopAnalyzer &vloop_analyzer);
// Attempt to run the SuperWord algorithm on the loop. Return true if we succeed.
bool transform_loop();
// Decide if loop can eventually be vectorized, and what unrolling factor is required.
static void unrolling_analysis(const VLoop &vloop, int &local_loop_unroll_factor);
// 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(); }
PhiNode* iv() const { return _vloop.iv(); }
int iv_stride() const { return cl()->stride_con(); }
bool in_bb(const Node* n) const { return _vloop.in_bb(n); }
// VLoopReductions accessors
bool is_marked_reduction(const Node* n) const {
return _vloop_analyzer.reductions().is_marked_reduction(n);
}
bool reduction(const Node* n1, const Node* n2) const {
return _vloop_analyzer.reductions().is_marked_reduction_pair(n1, n2);
}
// VLoopMemorySlices accessors
bool same_memory_slice(MemNode* n1, MemNode* n2) const {
return _vloop_analyzer.memory_slices().same_memory_slice(n1, n2);
}
// VLoopBody accessors
const GrowableArray<Node*>& body() const {
return _vloop_analyzer.body().body();
}
int bb_idx(const Node* n) const {
return _vloop_analyzer.body().bb_idx(n);
}
template<typename Callback>
void for_each_mem(Callback callback) const {
return _vloop_analyzer.body().for_each_mem(callback);
}
// VLoopTypes accessors
const Type* velt_type(Node* n) const {
return _vloop_analyzer.types().velt_type(n);
}
BasicType velt_basic_type(Node* n) const {
return _vloop_analyzer.types().velt_basic_type(n);
}
bool same_velt_type(Node* n1, Node* n2) const {
return _vloop_analyzer.types().same_velt_type(n1, n2);
}
int data_size(const Node* n) const {
return _vloop_analyzer.types().data_size(n);
}
int vector_width(Node* n) const {
return _vloop_analyzer.types().vector_width(n);
}
int vector_width_in_bytes(const Node* n) const {
return _vloop_analyzer.types().vector_width_in_bytes(n);
}
// VLoopDependencyGraph accessors
const VLoopDependencyGraph& dependency_graph() const {
return _vloop_analyzer.dependency_graph();
}
bool independent(Node* n1, Node* n2) const {
return _vloop_analyzer.dependency_graph().independent(n1, n2);
}
bool mutually_independent(const Node_List* nodes) const {
return _vloop_analyzer.dependency_graph().mutually_independent(nodes);
}
// VLoopVPointer accessors
const VPointer& vpointer(const MemNode* mem) const {
return _vloop_analyzer.vpointers().vpointer(mem);
}
#ifndef PRODUCT
// TraceAutoVectorization and TraceSuperWord
bool is_trace_superword_adjacent_memops() const {
return TraceSuperWord ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_ADJACENT_MEMOPS);
}
bool is_trace_superword_rejections() const {
return TraceSuperWord ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_REJECTIONS);
}
bool is_trace_superword_packset() const {
return TraceSuperWord ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_PACKSET);
}
bool is_trace_superword_info() const {
return TraceSuperWord ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_INFO);
}
bool is_trace_superword_any() const {
return TraceSuperWord ||
is_trace_align_vector() ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_ADJACENT_MEMOPS) ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_REJECTIONS) ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_PACKSET) ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_INFO) ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_VERBOSE);
}
bool is_trace_align_vector() const {
return _vloop.vtrace().is_trace(TraceAutoVectorizationTag::ALIGN_VECTOR) ||
_vloop.vtrace().is_trace(TraceAutoVectorizationTag::SW_VERBOSE);
}
#endif
bool do_vector_loop() { return _do_vector_loop; }
const PackSet& packset() const { return _packset; }
Node_List* get_pack(const Node* n) const { return _packset.get_pack(n); }
private:
bool _do_vector_loop; // whether to do vectorization/simd style
int _num_work_vecs; // Number of non memory vector operations
int _num_reductions; // Number of reduction expressions applied
// Accessors
Arena* arena() { return &_arena; }
// CloneMap utilities
bool same_origin_idx(Node* a, Node* b) const;
bool same_generation(Node* a, Node* b) const;
private:
bool SLP_extract();
// Find the "seed" memops pairs. These are pairs that we strongly suspect would lead to vectorization.
class MemOp : public StackObj {
private:
MemNode* _mem;
const VPointer* _vpointer;
int _original_index;
public:
// Empty, for GrowableArray
MemOp() :
_mem(nullptr),
_vpointer(nullptr),
_original_index(-1) {}
MemOp(MemNode* mem, const VPointer* vpointer, int original_index) :
_mem(mem),
_vpointer(vpointer),
_original_index(original_index) {}
MemNode* mem() const { return _mem; }
const VPointer& vpointer() const { return *_vpointer; }
int original_index() const { return _original_index; }
static int cmp_by_group(MemOp* a, MemOp* b);
static int cmp_by_group_and_con_and_original_index(MemOp* a, MemOp* b);
// We use two comparisons, because a subtraction could underflow.
template <typename T>
static int cmp_code(T a, T b) {
if (a < b) { return -1; }
if (a > b) { return 1; }
return 0;
}
};
void create_adjacent_memop_pairs();
void collect_valid_memops(GrowableArray<MemOp>& memops) const;
void create_adjacent_memop_pairs_in_all_groups(const GrowableArray<MemOp>& memops);
static int find_group_end(const GrowableArray<MemOp>& memops, int group_start);
void create_adjacent_memop_pairs_in_one_group(const GrowableArray<MemOp>& memops, const int group_start, int group_end);
// Various methods to check if we can pack two nodes.
bool can_pack_into_pair(Node* s1, Node* s2);
// Is s1 immediately before s2 in memory?
bool are_adjacent_refs(Node* s1, Node* s2) const;
// Are s1 and s2 similar?
bool isomorphic(Node* s1, Node* s2);
// Do we have pattern n1 = (iv + c) and n2 = (iv + c + 1)?
bool is_populate_index(const Node* n1, const Node* n2) const;
// For a node pair (s1, s2) which is isomorphic and independent,
// do s1 and s2 have similar input edges?
bool have_similar_inputs(Node* s1, Node* s2);
void extend_pairset_with_more_pairs_by_following_use_and_def();
bool extend_pairset_with_more_pairs_by_following_def(Node* s1, Node* s2);
bool extend_pairset_with_more_pairs_by_following_use(Node* s1, Node* s2);
void order_inputs_of_all_use_pairs_to_match_def_pair(Node* def1, Node* def2);
enum PairOrderStatus { Ordered, Unordered, Unknown };
PairOrderStatus order_inputs_of_uses_to_match_def_pair(Node* def1, Node* def2, Node* use1, Node* use2);
int estimate_cost_savings_when_packing_as_pair(const Node* s1, const Node* s2) const;
void combine_pairs_to_longer_packs();
void split_packs_at_use_def_boundaries();
void split_packs_only_implemented_with_smaller_size();
void split_packs_to_break_mutual_dependence();
void filter_packs_for_power_of_2_size();
void filter_packs_for_mutual_independence();
void filter_packs_for_alignment();
const AlignmentSolution* pack_alignment_solution(const Node_List* pack);
void filter_packs_for_implemented();
void filter_packs_for_profitable();
DEBUG_ONLY(void verify_packs() const;)
// Can code be generated for the pack, restricted to size nodes?
bool implemented(const Node_List* pack, const uint size) const;
// Find the maximal implemented size smaller or equal to the packs size
uint max_implemented_size(const Node_List* pack);
// For pack p, are all operands and all uses (with in the block) vector?
bool profitable(const Node_List* p) const;
// Verify that all uses of packs are also packs, i.e. we do not need extract operations.
DEBUG_ONLY(void verify_no_extract();)
// Check if n_super's pack uses are a superset of n_sub's pack uses.
bool has_use_pack_superset(const Node* n1, const Node* n2) const;
// Find a boundary in the pack, where left and right have different pack uses and defs.
uint find_use_def_boundary(const Node_List* pack) const;
// Is use->in(u_idx) a vector use?
bool is_vector_use(Node* use, int u_idx) const;
bool is_velt_basic_type_compatible_use_def(Node* use, Node* def) const;
bool schedule_and_apply() const;
};
#endif // SHARE_OPTO_SUPERWORD_HPP