// 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 #include #include #include #include #include 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>; /// 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& 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& 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_