blob: 0347bc3925602a1be48e04bee7e58323f6bb0b5d [file] [log] [blame]
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef SRC_READER_SPIRV_CONSTRUCT_H_
#define SRC_READER_SPIRV_CONSTRUCT_H_
#include <memory>
#include <sstream>
#include <string>
#include <vector>
namespace tint {
namespace reader {
namespace spirv {
/// A structured control flow construct, consisting of a set of basic blocks.
/// A construct is a span of blocks in the computed block order,
/// and will appear contiguously in the WGSL source.
///
/// SPIR-V (2.11 Structured Control Flow) defines:
/// - loop construct
/// - continue construct
/// - selection construct
/// We also define a "function construct" consisting of all the basic blocks in
/// the function.
///
/// The first block in a construct (by computed block order) is called a
/// "header". For the constructs defined by SPIR-V, the header block is the
/// basic block containing the merge instruction. The header for the function
/// construct is the entry block of the function.
///
/// Given two constructs A and B, we say "A encloses B" if B is a subset of A,
/// i.e. if every basic block in B is also in A. Note that a construct encloses
/// itself.
///
/// In a valid SPIR-V module, constructs will nest, meaning given
/// constructs A and B, either A encloses B, or B encloses A, or
/// or they are disjoint (have no basic blocks in commont).
///
/// A loop in a high level language translates into either:
//
/// - a single-block loop, where the loop header branches back to itself.
/// In this case this single-block loop consists only of the *continue
/// construct*. There is no "loop construct" for this case.
//
/// - a multi-block loop, where the loop back-edge is different from the loop
/// header.
/// This case has both a non-empty loop construct containing at least the
/// loop header, and a non-empty continue construct, containing at least the
/// back-edge block.
///
/// We care about two kinds of selection constructs:
///
/// - if-selection: where the header block ends in OpBranchConditional
///
/// - switch-selection: where the header block ends in OpSwitch
///
struct Construct {
/// Enumeration for the kinds of structured constructs.
enum Kind {
/// The whole function.
kFunction,
/// A SPIR-V selection construct, header basic block ending in
/// OpBrancConditional.
kIfSelection,
/// A SPIR-V selection construct, header basic block ending in OpSwitch.
kSwitchSelection,
/// A SPIR-V loop construct.
kLoop,
/// A SPIR-V continue construct.
kContinue,
};
/// Constructor
/// @param the_parent parent construct
/// @param the_depth construct nesting depth
/// @param the_kind construct kind
/// @param the_begin_id block id of the first block in the construct
/// @param the_end_id block id of the first block after the construct, or 0
/// @param the_begin_pos block order position of the_begin_id
/// @param the_end_pos block order position of the_end_id or a too-large value
/// @param the_scope_end_pos block position of the first block past the end of
/// the WGSL scope
Construct(const Construct* the_parent,
int the_depth,
Kind the_kind,
uint32_t the_begin_id,
uint32_t the_end_id,
uint32_t the_begin_pos,
uint32_t the_end_pos,
uint32_t the_scope_end_pos);
/// @param pos a block position
/// @returns true if the given block position is inside this construct.
bool ContainsPos(uint32_t pos) const {
return begin_pos <= pos && pos < end_pos;
}
/// Returns true if the given block position is inside the WGSL scope
/// corresponding to this construct. A loop construct's WGSL scope encloses
/// the associated continue construct. Otherwise the WGSL scope extent is the
/// same as the block extent.
/// @param pos a block position
/// @returns true if the given block position is inside the WGSL scope.
bool ScopeContainsPos(uint32_t pos) const {
return begin_pos <= pos && pos < scope_end_pos;
}
/// The nearest enclosing construct other than itself, or nullptr if
/// this construct represents the entire function.
const Construct* const parent = nullptr;
/// The nearest enclosing loop construct, if one exists. Points to `this`
/// when this is a loop construct.
const Construct* const enclosing_loop = nullptr;
/// The nearest enclosing continue construct, if one exists. Points to
/// `this` when this is a contnue construct.
const Construct* const enclosing_continue = nullptr;
/// The nearest enclosing loop construct or continue construct or
/// switch-selection construct, if one exists. The signficance is
/// that a high level language "break" will branch to the merge block
/// of such an enclosing construct. Points to `this` when this is
/// a loop construct, a continue construct, or a switch-selection construct.
const Construct* const enclosing_loop_or_continue_or_switch = nullptr;
/// Control flow nesting depth. The entry block is at nesting depth 0.
const int depth = 0;
/// The construct kind
const Kind kind = kFunction;
/// The id of the first block in this structure.
const uint32_t begin_id = 0;
/// 0 for kFunction, or the id of the block immediately after this construct
/// in the computed block order.
const uint32_t end_id = 0;
/// The position of block #begin_id in the computed block order.
const uint32_t begin_pos = 0;
/// The position of block #end_id in the block order, or the number of
/// block order elements if #end_id is 0.
const uint32_t end_pos = 0;
/// The position of the first block after the WGSL scope corresponding to
/// this construct.
const uint32_t scope_end_pos = 0;
};
using ConstructList = std::vector<std::unique_ptr<Construct>>;
/// Converts a construct kind to a string.
/// @param kind the construct kind to convert
/// @returns the string representation
inline std::string ToString(Construct::Kind kind) {
switch (kind) {
case Construct::kFunction:
return "Function";
case Construct::kIfSelection:
return "IfSelection";
case Construct::kSwitchSelection:
return "SwitchSelection";
case Construct::kLoop:
return "Loop";
case Construct::kContinue:
return "Continue";
}
return "NONE";
}
/// Converts a construct into a short summary string.
/// @param c the construct, which can be null
/// @returns a short summary string
inline std::string ToStringBrief(const Construct* c) {
if (c) {
std::stringstream ss;
ss << ToString(c->kind) << "@" << c->begin_id;
return ss.str();
}
return "null";
}
/// Emits a construct to a stream.
/// @param o the stream
/// @param c the structured construct
/// @returns the stream
inline std::ostream& operator<<(std::ostream& o, const Construct& c) {
o << "Construct{ " << ToString(c.kind) << " [" << c.begin_pos << ","
<< c.end_pos << ")"
<< " begin_id:" << c.begin_id << " end_id:" << c.end_id
<< " depth:" << c.depth;
o << " parent:" << ToStringBrief(c.parent);
if (c.scope_end_pos != c.end_pos) {
o << " scope:[" << c.begin_pos << "," << c.scope_end_pos << ")";
}
if (c.enclosing_loop) {
o << " in-l:" << ToStringBrief(c.enclosing_loop);
}
if (c.enclosing_continue) {
o << " in-c:" << ToStringBrief(c.enclosing_continue);
}
if ((c.enclosing_loop_or_continue_or_switch != c.enclosing_loop) &&
(c.enclosing_loop_or_continue_or_switch != c.enclosing_continue)) {
o << " in-c-l-s:" << ToStringBrief(c.enclosing_loop_or_continue_or_switch);
}
o << " }";
return o;
}
/// Emits a construct to a stream.
/// @param o the stream
/// @param c the structured construct
/// @returns the stream
inline std::ostream& operator<<(std::ostream& o,
const std::unique_ptr<Construct>& c) {
return o << *(c.get());
}
/// Converts a construct to a string.
/// @param c the construct
/// @returns the string representation
inline std::string ToString(const Construct& c) {
std::stringstream ss;
ss << c;
return ss.str();
}
/// Converts a construct to a string.
/// @param c the construct
/// @returns the string representation
inline std::string ToString(const Construct* c) {
return c ? ToString(*c) : ToStringBrief(c);
}
/// Converts a unique pointer to a construct to a string.
/// @param c the construct
/// @returns the string representation
inline std::string ToString(const std::unique_ptr<Construct>& c) {
return ToString(*(c.get()));
}
/// Emits a construct list to a stream.
/// @param o the stream
/// @param cl the construct list
/// @returns the stream
inline std::ostream& operator<<(std::ostream& o, const ConstructList& cl) {
o << "ConstructList{\n";
for (const auto& c : cl) {
o << " " << c << "\n";
}
o << "}";
return o;
}
/// Converts a construct list to a string.
/// @param cl the construct list
/// @returns the string representation
inline std::string ToString(const ConstructList& cl) {
std::stringstream ss;
ss << cl;
return ss.str();
}
} // namespace spirv
} // namespace reader
} // namespace tint
#endif // SRC_READER_SPIRV_CONSTRUCT_H_