| // 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_FUNCTION_H_ | |
| #define SRC_READER_SPIRV_FUNCTION_H_ | |
| #include <memory> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include "src/program_builder.h" | |
| #include "src/reader/spirv/construct.h" | |
| #include "src/reader/spirv/parser_impl.h" | |
| namespace tint { | |
| namespace reader { | |
| namespace spirv { | |
| /// Kinds of CFG edges. | |
| // | |
| // The edge kinds are used in many ways. | |
| // | |
| // For example, consider the edges leaving a basic block and going to distinct | |
| // targets. If the total number of kForward + kIfBreak + kCaseFallThrough edges | |
| // is more than 1, then the block must be a structured header, i.e. it needs | |
| // a merge instruction to declare the control flow divergence and associated | |
| // reconvergence point. Those those edge kinds count toward divergence | |
| // because SPIR-v is designed to easily map back to structured control flow | |
| // in GLSL (and C). In GLSL and C, those forward-flow edges don't have a | |
| // special statement to express them. The other forward edges: kSwitchBreak, | |
| // kLoopBreak, and kLoopContinue directly map to 'break', 'break', and | |
| // 'continue', respectively. | |
| enum class EdgeKind { | |
| // A back-edge: An edge from a node to one of its ancestors in a depth-first | |
| // search from the entry block. | |
| kBack, | |
| // An edge from a node to the merge block of the nearest enclosing switch, | |
| // where there is no intervening loop. | |
| kSwitchBreak, | |
| // An edge from a node to the merge block of the nearest enclosing loop, where | |
| // there is no intervening switch. | |
| // The source block is a "break block" as defined by SPIR-V. | |
| kLoopBreak, | |
| // An edge from a node in a loop body to the associated continue target, where | |
| // there are no other intervening loops or switches. | |
| // The source block is a "continue block" as defined by SPIR-V. | |
| kLoopContinue, | |
| // An edge from a node to the merge block of the nearest enclosing structured | |
| // construct, but which is neither a kSwitchBreak or a kLoopBreak. | |
| // This can only occur for an "if" selection, i.e. where the selection | |
| // header ends in OpBranchConditional. | |
| kIfBreak, | |
| // An edge from one switch case to the next sibling switch case. | |
| kCaseFallThrough, | |
| // None of the above. | |
| kForward | |
| }; | |
| /// Bookkeeping info for a basic block. | |
| struct BlockInfo { | |
| /// Constructor | |
| /// @param bb internal representation of the basic block | |
| explicit BlockInfo(const spvtools::opt::BasicBlock& bb); | |
| ~BlockInfo(); | |
| /// The internal representation of the basic block. | |
| const spvtools::opt::BasicBlock* basic_block; | |
| /// The ID of the OpLabel instruction that starts this block. | |
| uint32_t id = 0; | |
| /// The position of this block in the reverse structured post-order. | |
| uint32_t pos = 0; | |
| /// If this block is a header, then this is the ID of the merge block. | |
| uint32_t merge_for_header = 0; | |
| /// If this block is a loop header, then this is the ID of the continue | |
| /// target. | |
| uint32_t continue_for_header = 0; | |
| /// If this block is a merge, then this is the ID of the header. | |
| uint32_t header_for_merge = 0; | |
| /// If this block is a continue target, then this is the ID of the loop | |
| /// header. | |
| uint32_t header_for_continue = 0; | |
| /// Is this block a continue target which is its own loop header block? | |
| /// In this case the continue construct is the entire loop. The associated | |
| /// "loop construct" is empty, and not represented. | |
| bool is_continue_entire_loop = false; | |
| /// The immediately enclosing structured construct. If this block is not | |
| /// in the block order at all, then this is still nullptr. | |
| const Construct* construct = nullptr; | |
| /// Maps the ID of a successor block (in the CFG) to its edge classification. | |
| std::unordered_map<uint32_t, EdgeKind> succ_edge; | |
| /// The following fields record relationships among blocks in a selection | |
| /// construct for an OpSwitch instruction. | |
| /// If not null, then the pointed-at construct is a selection for an OpSwitch, | |
| /// and this block is a case target for it. We say this block "heads" the | |
| /// case construct. | |
| const Construct* case_head_for = nullptr; | |
| /// If not null, then the pointed-at construct is a selection for an OpSwitch, | |
| /// and this block is the default target for it. We say this block "heads" | |
| /// the default case construct. | |
| const Construct* default_head_for = nullptr; | |
| /// Is this a default target for a switch, and is it also the merge for its | |
| /// switch? | |
| bool default_is_merge = false; | |
| /// The list of switch values that cause a branch to this block. | |
| std::unique_ptr<std::vector<uint64_t>> case_values; | |
| /// The following fields record relationships among blocks in a selection | |
| /// construct for an OpBranchConditional instruction. | |
| /// When this block is an if-selection header, this is the edge kind | |
| /// for the true branch. | |
| EdgeKind true_kind = EdgeKind::kForward; | |
| /// When this block is an if-selection header, this is the edge kind | |
| /// for the false branch. | |
| EdgeKind false_kind = EdgeKind::kForward; | |
| /// If not 0, then this block is an if-selection header, and `true_head` is | |
| /// the target id of the true branch on the OpBranchConditional, and that | |
| /// target is inside the if-selection. | |
| uint32_t true_head = 0; | |
| /// If not 0, then this block is an if-selection header, and `false_head` | |
| /// is the target id of the false branch on the OpBranchConditional, and | |
| /// that target is inside the if-selection. | |
| uint32_t false_head = 0; | |
| /// If not 0, then this block is an if-selection header, and when following | |
| /// the flow via the true and false branches, control first reconverges at | |
| /// the block with ID `premerge_head`, and `premerge_head` is still inside | |
| /// the if-selection. | |
| uint32_t premerge_head = 0; | |
| /// If non-empty, then this block is an if-selection header, and control flow | |
| /// in the body must be guarded by a boolean flow variable with this name. | |
| /// This occurs when a block in this selection has both an if-break edge, and | |
| /// also a different normal forward edge but without a merge instruction. | |
| std::string flow_guard_name = ""; | |
| /// The result IDs that this block is responsible for declaring as a | |
| /// hoisted variable. | |
| /// @see DefInfo#requires_hoisted_def | |
| std::vector<uint32_t> hoisted_ids; | |
| /// A PhiAssignment represents the assignment of a value to the state | |
| /// variable associated with an OpPhi in a successor block. | |
| struct PhiAssignment { | |
| /// The ID of an OpPhi receiving a value from this basic block. | |
| uint32_t phi_id; | |
| /// The the value carried to the given OpPhi. | |
| uint32_t value; | |
| }; | |
| /// If this basic block branches to a visited basic block containing phis, | |
| /// then this is the list of writes to the variables associated those phis. | |
| std::vector<PhiAssignment> phi_assignments; | |
| /// The IDs of OpPhi instructions which require their associated state | |
| /// variable to be declared in this basic block. | |
| std::vector<uint32_t> phis_needing_state_vars; | |
| }; | |
| inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) { | |
| o << "BlockInfo{" | |
| << " id: " << bi.id << " pos: " << bi.pos | |
| << " merge_for_header: " << bi.merge_for_header | |
| << " continue_for_header: " << bi.continue_for_header | |
| << " header_for_merge: " << bi.header_for_merge | |
| << " is_continue_entire_loop: " << int(bi.is_continue_entire_loop) << "}"; | |
| return o; | |
| } | |
| /// Reasons for avoiding generating an intermediate value. | |
| enum class SkipReason { | |
| /// `kDontSkip`: The value should be generated. Used for most values. | |
| kDontSkip, | |
| /// For remaining cases, the value is not generated. | |
| /// `kOpaqueObject`: used for any intermediate value which is an sampler, | |
| /// image, | |
| /// or sampled image, or any pointer to such object. Code is generated | |
| /// for those objects only when emitting the image instructions that access | |
| /// the image (read, write, sample, gather, fetch, or query). For example, | |
| /// when encountering an OpImageSampleExplicitLod, a call to the | |
| /// textureSampleLevel builtin function will be emitted, and the call will | |
| /// directly reference the underlying texture and sampler (variable or | |
| /// function parameter). | |
| kOpaqueObject, | |
| /// `kPointSizeBuiltinPointer`: the value is a pointer to the Position builtin | |
| /// variable. Don't generate its address. Avoid generating stores to this | |
| /// pointer. | |
| kPointSizeBuiltinPointer, | |
| /// `kPointSizeBuiltinValue`: the value is the value loaded from the | |
| /// PointSize builtin. Use 1.0f instead, because that's the only value | |
| /// supported by WebGPU. | |
| kPointSizeBuiltinValue, | |
| /// `kSampleIdBuiltinPointer`: the value is a pointer to the SampleId builtin | |
| /// variable. Don't generate its address. | |
| kSampleIdBuiltinPointer, | |
| /// `kVertexIndexBuiltinPointer`: the value is a pointer to the VertexIndex | |
| /// builtin variable. Don't generate its address. | |
| kVertexIndexBuiltinPointer, | |
| /// `kInstanceIndexBuiltinPointer`: the value is a pointer to the | |
| /// InstanceIndex | |
| /// builtin variable. Don't generate its address. | |
| kInstanceIndexBuiltinPointer, | |
| /// `kSampleMaskInBuiltinPointer`: the value is a pointer to the SampleMaskIn | |
| /// builtin input variable. Don't generate its address. | |
| kSampleMaskInBuiltinPointer, | |
| /// `kSampleMaskOutBuiltinPointer`: the value is a pointer to the SampleMask | |
| /// builtin output variable. | |
| kSampleMaskOutBuiltinPointer, | |
| }; | |
| /// Bookkeeping info for a SPIR-V ID defined in the function, or some | |
| /// module-scope variables. This will be valid for result IDs that are: | |
| /// - defined in the function and: | |
| /// - instructions that are not OpLabel, and not OpFunctionParameter | |
| /// - are defined in a basic block visited in the block-order for the | |
| /// function. | |
| /// - certain module-scope builtin variables. | |
| struct DefInfo { | |
| /// Constructor. | |
| /// @param def_inst the SPIR-V instruction defining the ID | |
| /// @param block_pos the position of the basic block where the ID is defined. | |
| /// @param index an ordering index for this local definition | |
| DefInfo(const spvtools::opt::Instruction& def_inst, | |
| uint32_t block_pos, | |
| size_t index); | |
| /// Destructor. | |
| ~DefInfo(); | |
| /// The SPIR-V instruction that defines the ID. | |
| const spvtools::opt::Instruction& inst; | |
| /// The position of the first block in which this ID is visible, in function | |
| /// block order. For IDs defined outside of the function, it is 0. | |
| /// For IDs defined in the function, it is the position of the block | |
| /// containing the definition of the ID. | |
| /// See method `FunctionEmitter::ComputeBlockOrderAndPositions` | |
| const uint32_t block_pos = 0; | |
| /// An index for uniquely and deterministically ordering all DefInfo records | |
| /// in a function. | |
| const size_t index = 0; | |
| /// The number of uses of this ID. | |
| uint32_t num_uses = 0; | |
| /// The block position of the last use of this ID, or 0 if it is not used | |
| /// at all. The "last" ordering is determined by the function block order. | |
| uint32_t last_use_pos = 0; | |
| /// Is this value used in a construct other than the one in which it was | |
| /// defined? | |
| bool used_in_another_construct = false; | |
| /// True if this ID requires a WGSL 'const' definition, due to context. It | |
| /// might get one anyway (so this is *not* an if-and-only-if condition). | |
| bool requires_named_const_def = false; | |
| /// True if this ID must map to a WGSL variable declaration before the | |
| /// corresponding position of the ID definition in SPIR-V. This compensates | |
| /// for the difference between dominance and scoping. An SSA definition can | |
| /// dominate all its uses, but the construct where it is defined does not | |
| /// enclose all the uses, and so if it were declared as a WGSL constant | |
| /// definition at the point of its SPIR-V definition, then the WGSL name | |
| /// would go out of scope too early. Fix that by creating a variable at the | |
| /// top of the smallest construct that encloses both the definition and all | |
| /// its uses. Then the original SPIR-V definition maps to a WGSL assignment | |
| /// to that variable, and each SPIR-V use becomes a WGSL read from the | |
| /// variable. | |
| /// TODO(dneto): This works for constants of storable type, but not, for | |
| /// example, pointers. | |
| bool requires_hoisted_def = false; | |
| /// If the definition is an OpPhi, then `phi_var` is the name of the | |
| /// variable that stores the value carried from parent basic blocks into | |
| /// the basic block containing the OpPhi. Otherwise this is the empty string. | |
| std::string phi_var; | |
| /// The storage class to use for this value, if it is of pointer type. | |
| /// This is required to carry a storage class override from a storage | |
| /// buffer expressed in the old style (with Uniform storage class) | |
| /// that needs to be remapped to StorageBuffer storage class. | |
| /// This is kNone for non-pointers. | |
| ast::StorageClass storage_class = ast::StorageClass::kNone; | |
| /// The reason, if any, that this value should be ignored. | |
| /// Normally no values are ignored. This field can be updated while | |
| /// generating code because sometimes we only discover necessary facts | |
| /// in the middle of generating code. | |
| SkipReason skip = SkipReason::kDontSkip; | |
| }; | |
| inline std::ostream& operator<<(std::ostream& o, const DefInfo& di) { | |
| o << "DefInfo{" | |
| << " inst.result_id: " << di.inst.result_id() | |
| << " block_pos: " << di.block_pos << " num_uses: " << di.num_uses | |
| << " last_use_pos: " << di.last_use_pos << " requires_named_const_def: " | |
| << (di.requires_named_const_def ? "true" : "false") | |
| << " requires_hoisted_def: " << (di.requires_hoisted_def ? "true" : "false") | |
| << " phi_var: '" << di.phi_var << "'"; | |
| if (di.storage_class != ast::StorageClass::kNone) { | |
| o << " sc:" << int(di.storage_class); | |
| } | |
| switch (di.skip) { | |
| case SkipReason::kDontSkip: | |
| break; | |
| case SkipReason::kOpaqueObject: | |
| o << " skip:opaque"; | |
| break; | |
| case SkipReason::kPointSizeBuiltinPointer: | |
| o << " skip:pointsize_pointer"; | |
| break; | |
| case SkipReason::kPointSizeBuiltinValue: | |
| o << " skip:pointsize_value"; | |
| break; | |
| case SkipReason::kSampleIdBuiltinPointer: | |
| o << " skip:sampleid_pointer"; | |
| break; | |
| case SkipReason::kVertexIndexBuiltinPointer: | |
| o << " skip:vertexindex_pointer"; | |
| break; | |
| case SkipReason::kInstanceIndexBuiltinPointer: | |
| o << " skip:instanceindex_pointer"; | |
| break; | |
| case SkipReason::kSampleMaskInBuiltinPointer: | |
| o << " skip:samplemaskin_pointer"; | |
| break; | |
| case SkipReason::kSampleMaskOutBuiltinPointer: | |
| o << " skip:samplemaskout_pointer"; | |
| break; | |
| } | |
| o << "}"; | |
| return o; | |
| } | |
| /// A placeholder Statement that exists for the duration of building a | |
| /// StatementBlock. Once the StatementBlock is built, Build() will be called to | |
| /// construct the final AST node, which will be used in the place of this | |
| /// StatementBuilder. | |
| /// StatementBuilders are used to simplify construction of AST nodes that will | |
| /// become immutable. The builders may hold mutable state while the | |
| /// StatementBlock is being constructed, which becomes an immutable node on | |
| /// StatementBlock::Finalize(). | |
| class StatementBuilder : public Castable<StatementBuilder, ast::Statement> { | |
| public: | |
| /// Constructor | |
| StatementBuilder() : Base(ProgramID(), Source{}) {} | |
| /// @param builder the program builder | |
| /// @returns the build AST node | |
| virtual ast::Statement* Build(ProgramBuilder* builder) const = 0; | |
| private: | |
| Node* Clone(CloneContext*) const override; | |
| void to_str(const semantic::Info& sem, | |
| std::ostream& out, | |
| size_t indent) const override; | |
| }; | |
| /// A FunctionEmitter emits a SPIR-V function onto a Tint AST module. | |
| class FunctionEmitter { | |
| public: | |
| /// Creates a FunctionEmitter, and prepares to write to the AST module | |
| /// in `pi` | |
| /// @param pi a ParserImpl which has already executed BuildInternalModule | |
| /// @param function the function to emit | |
| FunctionEmitter(ParserImpl* pi, const spvtools::opt::Function& function); | |
| /// Creates a FunctionEmitter, and prepares to write to the AST module | |
| /// in `pi` | |
| /// @param pi a ParserImpl which has already executed BuildInternalModule | |
| /// @param function the function to emit | |
| /// @param ep_info entry point information for this function, or nullptr | |
| FunctionEmitter(ParserImpl* pi, | |
| const spvtools::opt::Function& function, | |
| const EntryPointInfo* ep_info); | |
| /// Destructor | |
| ~FunctionEmitter(); | |
| /// Emits the function to AST module. | |
| /// @return whether emission succeeded | |
| bool Emit(); | |
| /// @returns true if emission has not yet failed. | |
| bool success() const { return fail_stream_.status(); } | |
| /// @returns true if emission has failed. | |
| bool failed() const { return !success(); } | |
| /// Finalizes any StatementBuilders returns the body of the function. | |
| /// Must only be called once, and to be used only for testing. | |
| /// @returns the body of the function. | |
| const ast::StatementList ast_body(); | |
| /// Records failure. | |
| /// @returns a FailStream on which to emit diagnostics. | |
| FailStream& Fail() { return fail_stream_.Fail(); } | |
| /// @returns the parser implementation | |
| ParserImpl* parser() { return &parser_impl_; } | |
| /// Emits the function body, populating the bottom entry of the statements | |
| /// stack. | |
| /// @returns false if emission failed. | |
| bool EmitBody(); | |
| /// Records a mapping from block ID to a BlockInfo struct. | |
| /// Populates `block_info_` | |
| void RegisterBasicBlocks(); | |
| /// Verifies that terminators only branch to labels in the current function. | |
| /// Assumes basic blocks have been registered. | |
| /// @returns true if terminators are valid | |
| bool TerminatorsAreValid(); | |
| /// Populates merge-header cross-links and BlockInfo#is_continue_entire_loop. | |
| /// Also verifies that merge instructions go to blocks in the same function. | |
| /// Assumes basic blocks have been registered, and terminators are valid. | |
| /// @returns false if registration fails | |
| bool RegisterMerges(); | |
| /// Determines the output order for the basic blocks in the function. | |
| /// Populates `block_order_` and BlockInfo#pos. | |
| /// Assumes basic blocks have been registered. | |
| void ComputeBlockOrderAndPositions(); | |
| /// @returns the reverse structured post order of the basic blocks in | |
| /// the function. | |
| const std::vector<uint32_t>& block_order() const { return block_order_; } | |
| /// Verifies that the orderings among a structured header, continue target, | |
| /// and merge block are valid. Assumes block order has been computed, and | |
| /// merges are valid and recorded. | |
| /// @returns false if invalid nesting was detected | |
| bool VerifyHeaderContinueMergeOrder(); | |
| /// Labels each basic block with its nearest enclosing structured construct. | |
| /// Populates BlockInfo#construct and the `constructs_` list. | |
| /// Assumes terminators are valid and merges have been registered, block | |
| /// order has been computed, and each block is labeled with its position. | |
| /// Checks nesting of structured control flow constructs. | |
| /// @returns false if bad nesting has been detected | |
| bool LabelControlFlowConstructs(); | |
| /// @returns the structured constructs | |
| const ConstructList& constructs() const { return constructs_; } | |
| /// Marks blocks targets of a switch, either as the head of a case or | |
| /// as the default target. | |
| /// @returns false on failure | |
| bool FindSwitchCaseHeaders(); | |
| /// Classifies the successor CFG edges for the ordered basic blocks. | |
| /// Also checks validity of each edge (populates BlockInfo#succ_edge). | |
| /// Implicitly checks dominance rules for headers and continue constructs. | |
| /// Assumes each block has been labeled with its control flow construct. | |
| /// @returns false on failure | |
| bool ClassifyCFGEdges(); | |
| /// Marks the blocks within a selection construct that are the first blocks | |
| /// in the "then" clause, the "else" clause, and the "premerge" clause. | |
| /// The head of the premerge clause is the block, if it exists, at which | |
| /// control flow reconverges from the "then" and "else" clauses, but before | |
| /// before the merge block for that selection. The existence of a premerge | |
| /// should be an exceptional case, but is allowed by the structured control | |
| /// flow rules. | |
| /// @returns false if bad nesting has been detected. | |
| bool FindIfSelectionInternalHeaders(); | |
| /// Creates a DefInfo record for each module-scope builtin variable | |
| /// that should be handled specially. Either it's ignored, or its store | |
| /// type is converted on load. | |
| /// Populates the `def_info_` mapping for such IDs. | |
| /// @returns false on failure | |
| bool RegisterSpecialBuiltInVariables(); | |
| /// Creates a DefInfo record for each locally defined SPIR-V ID. | |
| /// Populates the `def_info_` mapping with basic results for such IDs. | |
| /// @returns false on failure | |
| bool RegisterLocallyDefinedValues(); | |
| /// Returns the Tint storage class for the given SPIR-V ID that is a | |
| /// pointer value. | |
| /// @param id a SPIR-V ID for a pointer value | |
| /// @returns the storage class | |
| ast::StorageClass GetStorageClassForPointerValue(uint32_t id); | |
| /// Remaps the storage class for the type of a locally-defined value, | |
| /// if necessary. If it's not a pointer type, or if its storage class | |
| /// already matches, then the result is a copy of the `type` argument. | |
| /// @param type the AST type | |
| /// @param result_id the SPIR-V ID for the locally defined value | |
| /// @returns an possibly updated type | |
| type::Type* RemapStorageClass(type::Type* type, uint32_t result_id); | |
| /// Marks locally defined values when they should get a 'const' | |
| /// definition in WGSL, or a 'var' definition at an outer scope. | |
| /// This occurs in several cases: | |
| /// - When a SPIR-V instruction might use the dynamically computed value | |
| /// only once, but the WGSL code might reference it multiple times. | |
| /// For example, this occurs for the vector operands of OpVectorShuffle. | |
| /// In this case the definition's DefInfo#requires_named_const_def property | |
| /// is set to true. | |
| /// - When a definition and at least one of its uses are not in the | |
| /// same structured construct. | |
| /// In this case the definition's DefInfo#requires_named_const_def property | |
| /// is set to true. | |
| /// - When a definition is in a construct that does not enclose all the | |
| /// uses. In this case the definition's DefInfo#requires_hoisted_def | |
| /// property is set to true. | |
| /// Updates the `def_info_` mapping. | |
| void FindValuesNeedingNamedOrHoistedDefinition(); | |
| /// Emits declarations of function variables. | |
| /// @returns false if emission failed. | |
| bool EmitFunctionVariables(); | |
| /// Emits statements in the body. | |
| /// @returns false if emission failed. | |
| bool EmitFunctionBodyStatements(); | |
| /// Emits a basic block. | |
| /// @param block_info the block to emit | |
| /// @returns false if emission failed. | |
| bool EmitBasicBlock(const BlockInfo& block_info); | |
| /// Emits an IfStatement, including its condition expression, and sets | |
| /// up the statement stack to accumulate subsequent basic blocks into | |
| /// the "then" and "else" clauses. | |
| /// @param block_info the if-selection header block | |
| /// @returns false if emission failed. | |
| bool EmitIfStart(const BlockInfo& block_info); | |
| /// Emits a SwitchStatement, including its condition expression, and sets | |
| /// up the statement stack to accumulate subsequent basic blocks into | |
| /// the default clause and case clauses. | |
| /// @param block_info the switch-selection header block | |
| /// @returns false if emission failed. | |
| bool EmitSwitchStart(const BlockInfo& block_info); | |
| /// Emits a LoopStatement, and pushes a new StatementBlock to accumulate | |
| /// the remaining instructions in the current block and subsequent blocks | |
| /// in the loop. | |
| /// @param construct the loop construct | |
| /// @returns false if emission failed. | |
| bool EmitLoopStart(const Construct* construct); | |
| /// Emits a ContinuingStatement, and pushes a new StatementBlock to accumulate | |
| /// the remaining instructions in the current block and subsequent blocks | |
| /// in the continue construct. | |
| /// @param construct the continue construct | |
| /// @returns false if emission failed. | |
| bool EmitContinuingStart(const Construct* construct); | |
| /// Emits the non-control-flow parts of a basic block, but only once. | |
| /// The `already_emitted` parameter indicates whether the code has already | |
| /// been emitted, and is used to signal that this invocation actually emitted | |
| /// it. | |
| /// @param block_info the block to emit | |
| /// @param already_emitted the block to emit | |
| /// @returns false if the code had not yet been emitted, but emission failed | |
| bool EmitStatementsInBasicBlock(const BlockInfo& block_info, | |
| bool* already_emitted); | |
| /// Emits code for terminators, but that aren't part of entering or | |
| /// resolving structured control flow. That is, if the basic block | |
| /// terminator calls for it, emit the fallthrough, break, continue, return, | |
| /// or kill commands. | |
| /// @param block_info the block with the terminator to emit (if any) | |
| /// @returns false if emission failed | |
| bool EmitNormalTerminator(const BlockInfo& block_info); | |
| /// Returns a new statement to represent the given branch representing a | |
| /// "normal" terminator, as in the sense of EmitNormalTerminator. If no | |
| /// WGSL statement is required, the statement will be nullptr. This method | |
| /// tries to avoid emitting a 'break' statement when that would be redundant | |
| /// in WGSL due to implicit breaking out of a switch. | |
| /// @param src_info the source block | |
| /// @param dest_info the destination block | |
| /// @returns the new statement, or a null statement | |
| ast::Statement* MakeBranch(const BlockInfo& src_info, | |
| const BlockInfo& dest_info) const { | |
| return MakeBranchDetailed(src_info, dest_info, false, nullptr); | |
| } | |
| /// Returns a new statement to represent the given branch representing a | |
| /// "normal" terminator, as in the sense of EmitNormalTerminator. If no | |
| /// WGSL statement is required, the statement will be nullptr. | |
| /// @param src_info the source block | |
| /// @param dest_info the destination block | |
| /// @returns the new statement, or a null statement | |
| ast::Statement* MakeForcedBranch(const BlockInfo& src_info, | |
| const BlockInfo& dest_info) const { | |
| return MakeBranchDetailed(src_info, dest_info, true, nullptr); | |
| } | |
| /// Returns a new statement to represent the given branch representing a | |
| /// "normal" terminator, as in the sense of EmitNormalTerminator. If no | |
| /// WGSL statement is required, the statement will be nullptr. When `forced` | |
| /// is false, this method tries to avoid emitting a 'break' statement when | |
| /// that would be redundant in WGSL due to implicit breaking out of a switch. | |
| /// When `forced` is true, the method won't try to avoid emitting that break. | |
| /// If the control flow edge is an if-break for an if-selection with a | |
| /// control flow guard, then return that guard name via `flow_guard_name_ptr` | |
| /// when that parameter is not null. | |
| /// @param src_info the source block | |
| /// @param dest_info the destination block | |
| /// @param forced if true, always emit the branch (if it exists in WGSL) | |
| /// @param flow_guard_name_ptr return parameter for control flow guard name | |
| /// @returns the new statement, or a null statement | |
| ast::Statement* MakeBranchDetailed(const BlockInfo& src_info, | |
| const BlockInfo& dest_info, | |
| bool forced, | |
| std::string* flow_guard_name_ptr) const; | |
| /// Returns a new if statement with the given statements as the then-clause | |
| /// and the else-clause. Either or both clauses might be nullptr. If both | |
| /// are nullptr, then don't make a new statement and instead return nullptr. | |
| /// @param condition the branching condition | |
| /// @param then_stmt the statement for the then clause of the if, or nullptr | |
| /// @param else_stmt the statement for the else clause of the if, or nullptr | |
| /// @returns the new statement, or nullptr | |
| ast::Statement* MakeSimpleIf(ast::Expression* condition, | |
| ast::Statement* then_stmt, | |
| ast::Statement* else_stmt) const; | |
| /// Emits the statements for an normal-terminator OpBranchConditional | |
| /// where one branch is a case fall through (the true branch if and only | |
| /// if `fall_through_is_true_branch` is true), and the other branch is | |
| /// goes to a different destination, named by `other_dest`. | |
| /// @param src_info the basic block from which we're branching | |
| /// @param cond the branching condition | |
| /// @param other_edge_kind the edge kind from the source block to the other | |
| /// destination | |
| /// @param other_dest the other branching destination | |
| /// @param fall_through_is_true_branch true when the fall-through is the true | |
| /// branch | |
| /// @returns the false if emission fails | |
| bool EmitConditionalCaseFallThrough(const BlockInfo& src_info, | |
| ast::Expression* cond, | |
| EdgeKind other_edge_kind, | |
| const BlockInfo& other_dest, | |
| bool fall_through_is_true_branch); | |
| /// Emits a normal instruction: not a terminator, label, or variable | |
| /// declaration. | |
| /// @param inst the instruction | |
| /// @returns false if emission failed. | |
| bool EmitStatement(const spvtools::opt::Instruction& inst); | |
| /// Emits a const definition for the typed value in `ast_expr`, and | |
| /// records it as the translation for the result ID from `inst`. | |
| /// @param inst the SPIR-V instruction defining the value | |
| /// @param ast_expr the already-computed AST expression for the value | |
| /// @returns false if emission failed. | |
| bool EmitConstDefinition(const spvtools::opt::Instruction& inst, | |
| TypedExpression ast_expr); | |
| /// Emits a write of the typed value in `ast_expr` to a hoisted variable | |
| /// for the given SPIR-V ID, if that ID has a hoisted declaration. Otherwise, | |
| /// emits a const definition instead. | |
| /// @param inst the SPIR-V instruction defining the value | |
| /// @param ast_expr the already-computed AST expression for the value | |
| /// @returns false if emission failed. | |
| bool EmitConstDefOrWriteToHoistedVar(const spvtools::opt::Instruction& inst, | |
| TypedExpression ast_expr); | |
| /// Makes an expression | |
| /// @param id the SPIR-V ID of the value | |
| /// @returns true if emission has not yet failed. | |
| TypedExpression MakeExpression(uint32_t id); | |
| /// Creates an expression and supporting statements for a combinatorial | |
| /// instruction, or returns null. A SPIR-V instruction is combinatorial | |
| /// if it has no side effects and its result depends only on its operands, | |
| /// and not on accessing external state like memory or the state of other | |
| /// invocations. Statements are only created if required to provide values | |
| /// to the expression. Supporting statements are not required to be | |
| /// combinatorial. | |
| /// @param inst a SPIR-V instruction representing an exrpression | |
| /// @returns an AST expression for the instruction, or nullptr. | |
| TypedExpression MaybeEmitCombinatorialValue( | |
| const spvtools::opt::Instruction& inst); | |
| /// Creates an expression and supporting statements for the a GLSL.std.450 | |
| /// extended instruction. | |
| /// @param inst a SPIR-V OpExtInst instruction from GLSL.std.450 | |
| /// @returns an AST expression for the instruction, or nullptr. | |
| TypedExpression EmitGlslStd450ExtInst(const spvtools::opt::Instruction& inst); | |
| /// Creates an expression for OpCompositeExtract | |
| /// @param inst an OpCompositeExtract instruction. | |
| /// @returns an AST expression for the instruction, or nullptr. | |
| TypedExpression MakeCompositeExtract(const spvtools::opt::Instruction& inst); | |
| /// Creates an expression for indexing into a composite value. The literal | |
| /// indices that step into the value start at instruction input operand | |
| /// `start_index` and run to the end of the instruction. | |
| /// @param inst the original instruction | |
| /// @param composite the typed expression for the composite | |
| /// @param composite_type_id the SPIR-V type ID for the composite | |
| /// @param index_start the index of the first operand in `inst` that is an | |
| /// index into the composite type | |
| /// @returns an AST expression for the decomposed composite, or {} on error | |
| TypedExpression MakeCompositeValueDecomposition( | |
| const spvtools::opt::Instruction& inst, | |
| TypedExpression composite, | |
| uint32_t composite_type_id, | |
| int index_start); | |
| /// Creates an expression for OpVectorShuffle | |
| /// @param inst an OpVectorShuffle instruction. | |
| /// @returns an AST expression for the instruction, or nullptr. | |
| TypedExpression MakeVectorShuffle(const spvtools::opt::Instruction& inst); | |
| /// Creates an expression for a numeric conversion. | |
| /// @param inst a numeric conversion instruction | |
| /// @returns an AST expression for the instruction, or nullptr. | |
| TypedExpression MakeNumericConversion(const spvtools::opt::Instruction& inst); | |
| /// Gets the block info for a block ID, if any exists | |
| /// @param id the SPIR-V ID of the OpLabel instruction starting the block | |
| /// @returns the block info for the given ID, if it exists, or nullptr | |
| BlockInfo* GetBlockInfo(uint32_t id) const { | |
| auto where = block_info_.find(id); | |
| if (where == block_info_.end()) { | |
| return nullptr; | |
| } | |
| return where->second.get(); | |
| } | |
| /// Gets the local definition info for a result ID. | |
| /// @param id the SPIR-V ID of local definition. | |
| /// @returns the definition info for the given ID, if it exists, or nullptr | |
| DefInfo* GetDefInfo(uint32_t id) const { | |
| auto where = def_info_.find(id); | |
| if (where == def_info_.end()) { | |
| return nullptr; | |
| } | |
| return where->second.get(); | |
| } | |
| /// Returns the skip reason for a result ID. | |
| /// @param id SPIR-V result ID | |
| /// @returns the skip reason for the given ID, or SkipReason::kDontSkip | |
| SkipReason GetSkipReason(uint32_t id) const { | |
| if (auto* def_info = GetDefInfo(id)) { | |
| return def_info->skip; | |
| } | |
| return SkipReason::kDontSkip; | |
| } | |
| /// Returns the WGSL variable name for an input builtin variable whose | |
| /// translation is managed via the SkipReason mechanism. | |
| /// @param skip_reason the skip reason for the special variable | |
| /// @returns the variable name for a special builtin variable | |
| /// that is handled via the "skip" mechanism. | |
| std::string NameForSpecialInputBuiltin(SkipReason skip_reason); | |
| /// Returns the most deeply nested structured construct which encloses the | |
| /// WGSL scopes of names declared in both block positions. Each position must | |
| /// be a valid index into the function block order array. | |
| /// @param first_pos the first block position | |
| /// @param last_pos the last block position | |
| /// @returns the smallest construct containing both positions | |
| const Construct* GetEnclosingScope(uint32_t first_pos, | |
| uint32_t last_pos) const; | |
| /// Finds loop construct associated with a continue construct, if it exists. | |
| /// Returns nullptr if: | |
| /// - the given construct is not a continue construct | |
| /// - the continue construct does not have an associated loop construct | |
| /// (the continue target is also the loop header block) | |
| /// @param c the continue construct | |
| /// @returns the associated loop construct, or nullptr | |
| const Construct* SiblingLoopConstruct(const Construct* c) const; | |
| /// Returns an identifier expression for the swizzle name of the given | |
| /// index into a vector. Emits an error and returns nullptr if the | |
| /// index is out of range, i.e. 4 or higher. | |
| /// @param i index of the subcomponent | |
| /// @returns the identifier expression for the `i`'th component | |
| ast::IdentifierExpression* Swizzle(uint32_t i); | |
| /// Returns an identifier expression for the swizzle name of the first | |
| /// `n` elements of a vector. Emits an error and returns nullptr if `n` | |
| /// is out of range, i.e. 4 or higher. | |
| /// @param n the number of components in the swizzle | |
| /// @returns the swizzle identifier for the first n elements of a vector | |
| ast::IdentifierExpression* PrefixSwizzle(uint32_t n); | |
| /// Converts SPIR-V image coordinates from an image access instruction | |
| /// (e.g. OpImageSampledImplicitLod) into an expression list consisting of | |
| /// the texture coordinates, and an integral array index if the texture is | |
| /// arrayed. The texture coordinate is a scalar for 1D textures, a vector of | |
| /// 2 elements for a 2D texture, and a vector of 3 elements for a 3D or | |
| /// Cube texture. Excess components are ignored, e.g. if the SPIR-V | |
| /// coordinate is a 4-element vector but the image is a 2D non-arrayed | |
| /// texture then the 3rd and 4th components are ignored. | |
| /// On failure, issues an error and returns an empty expression list. | |
| /// @param image_access the image access instruction | |
| /// @returns an ExpressionList of the coordinate and array index (if any) | |
| ast::ExpressionList MakeCoordinateOperandsForImageAccess( | |
| const spvtools::opt::Instruction& image_access); | |
| /// Returns the given value as an I32. If it's already an I32 then this | |
| /// return the given value. Otherwise, wrap the value in a TypeConstructor | |
| /// expression. | |
| /// @param value the value to pass through or convert | |
| /// @returns the value as an I32 value. | |
| TypedExpression ToI32(TypedExpression value); | |
| /// Returns the given value as a signed integer type of the same shape | |
| /// if the value is unsigned scalar or vector, by wrapping the value | |
| /// with a TypeConstructor expression. Returns the value itself if the | |
| /// value otherwise. | |
| /// @param value the value to pass through or convert | |
| /// @returns the value itself, or converted to signed integral | |
| TypedExpression ToSignedIfUnsigned(TypedExpression value); | |
| private: | |
| /// FunctionDeclaration contains the parsed information for a function header. | |
| struct FunctionDeclaration { | |
| /// Constructor | |
| FunctionDeclaration(); | |
| /// Destructor | |
| ~FunctionDeclaration(); | |
| /// Parsed header source | |
| Source source; | |
| /// Function name | |
| std::string name; | |
| /// Function parameters | |
| ast::VariableList params; | |
| /// Function return type | |
| type::Type* return_type; | |
| /// Function decorations | |
| ast::DecorationList decorations; | |
| }; | |
| /// Parse the function declaration, which comprises the name, parameters, and | |
| /// return type, populating `decl`. | |
| /// @param decl the FunctionDeclaration to populate | |
| /// @returns true if emission has not yet failed. | |
| bool ParseFunctionDeclaration(FunctionDeclaration* decl); | |
| /// @returns the store type for the OpVariable instruction, or | |
| /// null on failure. | |
| type::Type* GetVariableStoreType( | |
| const spvtools::opt::Instruction& var_decl_inst); | |
| /// Returns an expression for an instruction operand. Signedness conversion is | |
| /// performed to match the result type of the SPIR-V instruction. | |
| /// @param inst the SPIR-V instruction | |
| /// @param operand_index the index of the operand, counting 0 as the first | |
| /// input operand | |
| /// @returns a new expression node | |
| TypedExpression MakeOperand(const spvtools::opt::Instruction& inst, | |
| uint32_t operand_index); | |
| /// Returns an expression for a SPIR-V OpAccessChain or OpInBoundsAccessChain | |
| /// instruction. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an expression | |
| TypedExpression MakeAccessChain(const spvtools::opt::Instruction& inst); | |
| /// Emits a function call. On failure, emits a diagnostic and returns false. | |
| /// @param inst the SPIR-V function call instruction | |
| /// @returns false if emission failed | |
| bool EmitFunctionCall(const spvtools::opt::Instruction& inst); | |
| /// Emits a control barrier intrinsic. On failure, emits a diagnostic and | |
| /// returns false. | |
| /// @param inst the SPIR-V control barrier instruction | |
| /// @returns false if emission failed | |
| bool EmitControlBarrier(const spvtools::opt::Instruction& inst); | |
| /// Returns an expression for a SPIR-V instruction that maps to a WGSL | |
| /// intrinsic function call. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an expression | |
| TypedExpression MakeIntrinsicCall(const spvtools::opt::Instruction& inst); | |
| /// Returns an expression for a SPIR-V OpArrayLength instruction. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an expression | |
| TypedExpression MakeArrayLength(const spvtools::opt::Instruction& inst); | |
| /// Generates an expression for a SPIR-V OpOuterProduct instruction. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an expression | |
| TypedExpression MakeOuterProduct(const spvtools::opt::Instruction& inst); | |
| /// Generates statements for a SPIR-V OpVectorInsertDynamic instruction. | |
| /// Registers a const declaration for the result. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an expression | |
| bool MakeVectorInsertDynamic(const spvtools::opt::Instruction& inst); | |
| /// Generates statements for a SPIR-V OpComposite instruction. | |
| /// Registers a const declaration for the result. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an expression | |
| bool MakeCompositeInsert(const spvtools::opt::Instruction& inst); | |
| /// Get the SPIR-V instruction for the image memory object declaration for | |
| /// the image operand to the given instruction. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns a SPIR-V OpVariable or OpFunctionParameter instruction, or null | |
| /// on error | |
| const spvtools::opt::Instruction* GetImage( | |
| const spvtools::opt::Instruction& inst); | |
| /// Get the AST texture the SPIR-V image memory object declaration. | |
| /// @param inst the SPIR-V memory object declaration for the image. | |
| /// @returns a texture type, or null on error | |
| type::Texture* GetImageType(const spvtools::opt::Instruction& inst); | |
| /// Get the expression for the image operand from the first operand to the | |
| /// given instruction. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an identifier expression, or null on error | |
| ast::Expression* GetImageExpression(const spvtools::opt::Instruction& inst); | |
| /// Get the expression for the sampler operand from the first operand to the | |
| /// given instruction. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an identifier expression, or null on error | |
| ast::Expression* GetSamplerExpression(const spvtools::opt::Instruction& inst); | |
| /// Emits a texture builtin function call for a SPIR-V instruction that | |
| /// accesses an image or sampled image. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an expression | |
| bool EmitImageAccess(const spvtools::opt::Instruction& inst); | |
| /// Emits statements to implement a SPIR-V image query. | |
| /// @param inst the SPIR-V instruction | |
| /// @returns an expression | |
| bool EmitImageQuery(const spvtools::opt::Instruction& inst); | |
| /// Converts the given texel to match the type required for the storage | |
| /// texture with the given type. This can generate a swizzle to retain | |
| /// only the first few components of the texel vector, and maybe a bitcast | |
| /// to convert signedness. Returns an expression, or emits an error and | |
| /// returns nullptr. | |
| /// @param inst the image access instruction (used for diagnostics) | |
| /// @param texel the texel | |
| /// @param texture_type the type of the storage texture | |
| /// @returns the texel, after necessary conversion. | |
| ast::Expression* ConvertTexelForStorage( | |
| const spvtools::opt::Instruction& inst, | |
| TypedExpression texel, | |
| type::Texture* texture_type); | |
| /// Returns an expression for an OpSelect, if its operands are scalars | |
| /// or vectors. These translate directly to WGSL select. Otherwise, return | |
| /// an expression with a null owned expression | |
| /// @param inst the SPIR-V OpSelect instruction | |
| /// @returns a typed expression, or one with a null owned expression | |
| TypedExpression MakeSimpleSelect(const spvtools::opt::Instruction& inst); | |
| /// Finds the header block for a structured construct that we can "break" | |
| /// out from, from deeply nested control flow, if such a block exists. | |
| /// If the construct is: | |
| /// - a switch selection: return the selection header (ending in OpSwitch) | |
| /// - a loop construct: return the loop header block | |
| /// - a continue construct: return the loop header block | |
| /// Otherwise, return nullptr. | |
| /// @param c a structured construct, or nullptr | |
| /// @returns the block info for the structured header we can "break" from, | |
| /// or nullptr | |
| BlockInfo* HeaderIfBreakable(const Construct* c); | |
| /// Appends a new statement to the top of the statement stack. | |
| /// Does nothing if the statement is null. | |
| /// @param statement the new statement | |
| /// @returns a pointer to the statement. | |
| ast::Statement* AddStatement(ast::Statement* statement); | |
| /// AddStatementBuilder() constructs and adds the StatementBuilder of type | |
| /// `T` to the top of the statement stack. | |
| /// @param args the arguments forwarded to the T constructor | |
| /// @return the built StatementBuilder | |
| template <typename T, typename... ARGS> | |
| T* AddStatementBuilder(ARGS&&... args) { | |
| TINT_ASSERT(!statements_stack_.empty()); | |
| return statements_stack_.back().AddStatementBuilder<T>( | |
| std::forward<ARGS>(args)...); | |
| } | |
| /// Returns the source record for the given instruction. | |
| /// @param inst the SPIR-V instruction | |
| /// @return the Source record, or a default one | |
| Source GetSourceForInst(const spvtools::opt::Instruction& inst) const; | |
| /// @returns the last statetment in the top of the statement stack. | |
| ast::Statement* LastStatement(); | |
| using CompletionAction = std::function<void(const ast::StatementList&)>; | |
| // A StatementBlock represents a braced-list of statements while it is being | |
| // constructed. | |
| class StatementBlock { | |
| public: | |
| StatementBlock(const Construct* construct, | |
| uint32_t end_id, | |
| CompletionAction completion_action); | |
| StatementBlock(StatementBlock&&); | |
| ~StatementBlock(); | |
| StatementBlock(const StatementBlock&) = delete; | |
| StatementBlock& operator=(const StatementBlock&) = delete; | |
| /// Replaces any StatementBuilders with the built result, and calls the | |
| /// completion callback (if set). Must only be called once, after all | |
| /// statements have been added with Add(). | |
| /// @param builder the program builder | |
| void Finalize(ProgramBuilder* builder); | |
| /// Add() adds `statement` to the block. | |
| /// Add() must not be called after calling Finalize(). | |
| void Add(ast::Statement* statement); | |
| /// AddStatementBuilder() constructs and adds the StatementBuilder of type | |
| /// `T` to the block. | |
| /// Add() must not be called after calling Finalize(). | |
| /// @param args the arguments forwarded to the T constructor | |
| /// @return the built StatementBuilder | |
| template <typename T, typename... ARGS> | |
| T* AddStatementBuilder(ARGS&&... args) { | |
| auto builder = std::make_unique<T>(std::forward<ARGS>(args)...); | |
| auto* ptr = builder.get(); | |
| Add(ptr); | |
| builders_.emplace_back(std::move(builder)); | |
| return ptr; | |
| } | |
| /// @param construct the construct which this construct constributes to | |
| void SetConstruct(const Construct* construct) { construct_ = construct; } | |
| /// @return the construct to which this construct constributes | |
| const Construct* GetConstruct() const { return construct_; } | |
| /// @return the ID of the block at which the completion action should be | |
| /// triggered and this statement block discarded. This is often the `end_id` | |
| /// of `construct` itself. | |
| uint32_t GetEndId() const { return end_id_; } | |
| /// @return the list of statements being built, if this construct is not a | |
| /// switch. | |
| const ast::StatementList& GetStatements() const { return statements_; } | |
| private: | |
| /// The construct to which this construct constributes. | |
| const Construct* construct_; | |
| /// The ID of the block at which the completion action should be triggered | |
| /// and this statement block discarded. This is often the `end_id` of | |
| /// `construct` itself. | |
| uint32_t const end_id_; | |
| /// The completion action finishes processing this statement block. | |
| FunctionEmitter::CompletionAction const completion_action_; | |
| /// The list of statements being built, if this construct is not a switch. | |
| ast::StatementList statements_; | |
| /// Owned statement builders | |
| std::vector<std::unique_ptr<StatementBuilder>> builders_; | |
| /// True if Finalize() has been called. | |
| bool finalized_ = false; | |
| }; | |
| /// Pushes an empty statement block onto the statements stack. | |
| /// @param action the completion action for this block | |
| void PushNewStatementBlock(const Construct* construct, | |
| uint32_t end_id, | |
| CompletionAction action); | |
| /// Emits an if-statement whose condition is the given flow guard | |
| /// variable, and pushes onto the statement stack the corresponding | |
| /// statement block ending (and not including) the given block. | |
| /// @param flow_guard name of the flow guard variable | |
| /// @param end_id first block after the if construct. | |
| void PushGuard(const std::string& flow_guard, uint32_t end_id); | |
| /// Emits an if-statement with 'true' condition, and pushes onto the | |
| /// statement stack the corresponding statement block ending (and not | |
| /// including) the given block. | |
| /// @param end_id first block after the if construct. | |
| void PushTrueGuard(uint32_t end_id); | |
| /// @returns a boolean true expression. | |
| ast::Expression* MakeTrue(const Source&) const; | |
| /// @returns a boolean false expression. | |
| ast::Expression* MakeFalse(const Source&) const; | |
| /// Creates a new `ast::Node` owned by the ProgramBuilder. | |
| /// @param args the arguments to pass to the type constructor | |
| /// @returns the node pointer | |
| template <typename T, typename... ARGS> | |
| T* create(ARGS&&... args) const { | |
| return builder_.create<T>(std::forward<ARGS>(args)...); | |
| } | |
| using StatementsStack = std::vector<StatementBlock>; | |
| ParserImpl& parser_impl_; | |
| ProgramBuilder& builder_; | |
| spvtools::opt::IRContext& ir_context_; | |
| spvtools::opt::analysis::DefUseManager* def_use_mgr_; | |
| spvtools::opt::analysis::ConstantManager* constant_mgr_; | |
| spvtools::opt::analysis::TypeManager* type_mgr_; | |
| FailStream& fail_stream_; | |
| Namer& namer_; | |
| const spvtools::opt::Function& function_; | |
| type::I32* const i32_; // The unique I32 type object. | |
| type::U32* const u32_; // The unique U32 type object. | |
| // The SPIR-V ID for the SampleMask input variable. | |
| uint32_t sample_mask_in_id; | |
| // The SPIR-V ID for the SampleMask output variable. | |
| uint32_t sample_mask_out_id; | |
| // A stack of statement lists. Each list is contained in a construct in | |
| // the next deeper element of stack. The 0th entry represents the statements | |
| // for the entire function. This stack is never empty. | |
| // The `construct` member for the 0th element is only valid during the | |
| // lifetime of the EmitFunctionBodyStatements method. | |
| StatementsStack statements_stack_; | |
| // The set of IDs that have already had an identifier name generated for it. | |
| std::unordered_set<uint32_t> identifier_values_; | |
| // Mapping from SPIR-V ID that is used at most once, to its AST expression. | |
| std::unordered_map<uint32_t, TypedExpression> singly_used_values_; | |
| // The IDs of basic blocks, in reverse structured post-order (RSPO). | |
| // This is the output order for the basic blocks. | |
| std::vector<uint32_t> block_order_; | |
| // Mapping from block ID to its bookkeeping info. | |
| std::unordered_map<uint32_t, std::unique_ptr<BlockInfo>> block_info_; | |
| // Mapping from a locally-defined result ID to its bookkeeping info. | |
| std::unordered_map<uint32_t, std::unique_ptr<DefInfo>> def_info_; | |
| // Structured constructs, where enclosing constructs precede their children. | |
| ConstructList constructs_; | |
| // Information about entry point, if this function is referenced by one | |
| const EntryPointInfo* ep_info_ = nullptr; | |
| }; | |
| } // namespace spirv | |
| } // namespace reader | |
| } // namespace tint | |
| #endif // SRC_READER_SPIRV_FUNCTION_H_ |