dawn-cmake/src/reader/spirv/construct.h
David Neto 6453bee795 [spirv-reader] Even better hoisted variables
Use the fact that in WGSL the scope corresponding to the loop construct
encloses the scope for its associated continue construct.
In our construct data structure, the two are adjacent but not
overlapping.

This improvement means that when a definition is in a loop construct,
but used only in the loop or associated continue construct, then no
hoisting is required.

Bug: tint:3
Change-Id: I8d33b8f76303ab2868306847e846b4c26899e746
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/24420
Reviewed-by: dan sinclair <dsinclair@google.com>
2020-07-07 17:25:48 +00:00

281 lines
9.5 KiB
C++

// 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 <cstdint>
#include <memory>
#include <ostream>
#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_