1 // Copyright (c) 2020 Vasyl Teliman 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_DOWN_H_ 16 #define SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_DOWN_H_ 17 18 #include <map> 19 20 #include "source/fuzz/protobufs/spirvfuzz_protobufs.h" 21 #include "source/fuzz/transformation.h" 22 #include "source/fuzz/transformation_context.h" 23 #include "source/opt/ir_context.h" 24 25 namespace spvtools { 26 namespace fuzz { 27 28 class TransformationPropagateInstructionDown : public Transformation { 29 public: 30 explicit TransformationPropagateInstructionDown( 31 const protobufs::TransformationPropagateInstructionDown& message); 32 33 TransformationPropagateInstructionDown( 34 uint32_t block_id, uint32_t phi_fresh_id, 35 const std::map<uint32_t, uint32_t>& successor_id_to_fresh_id); 36 37 // - It should be possible to apply this transformation to |block_id| (see 38 // IsApplicableToBlock method). 39 // - Every acceptable successor of |block_id| (see GetAcceptableSuccessors 40 // method) must have an entry in the |successor_id_to_fresh_id| map unless 41 // overflow ids are available. 42 // - All values in |successor_id_to_fresh_id| and |phi_fresh_id| must be 43 // unique and fresh. 44 bool IsApplicable( 45 opt::IRContext* ir_context, 46 const TransformationContext& transformation_context) const override; 47 48 // - Adds a clone of the propagated instruction into every acceptable 49 // successor of |block_id|. 50 // - Removes the original instruction. 51 // - Creates an OpPhi instruction if possible, that tries to group created 52 // clones. 53 // - If the original instruction's id was irrelevant - marks created 54 // instructions as irrelevant. Otherwise, marks the created instructions as 55 // synonymous to each other if possible (i.e. skips instructions, copied 56 // into dead blocks). 57 void Apply(opt::IRContext* ir_context, 58 TransformationContext* transformation_context) const override; 59 60 protobufs::Transformation ToMessage() const override; 61 62 // Returns true if this transformation can be applied to the block with id 63 // |block_id|. Concretely, returns true iff: 64 // - |block_id| is a result id of some reachable basic block in the module. 65 // - the block has an instruction to propagate (see 66 // GetInstructionToPropagate method). 67 // - the block has at least one acceptable successor (see 68 // GetAcceptableSuccessors method). 69 // - none of the acceptable successors have OpPhi instructions that use the 70 // original instruction. 71 // - it is possible to replace every use of the original instruction with some 72 // of the propagated instructions (or an OpPhi if we can create it - see 73 // GetOpPhiBlockId method). 74 static bool IsApplicableToBlock(opt::IRContext* ir_context, 75 uint32_t block_id); 76 77 // Returns ids of successors of |block_id|, that can be used to propagate an 78 // instruction into. Concretely, a successor block is acceptable if all 79 // dependencies of the propagated instruction dominate it. Note that this 80 // implies that an acceptable successor must be reachable in the CFG. 81 // For example: 82 // %1 = OpLabel 83 // OpSelectionMerge %2 None 84 // OpBranchConditional %cond %2 %3 85 // %3 = OpLabel 86 // %4 = OpUndef %int 87 // %5 = OpCopyObject %int %4 88 // OpBranch %2 89 // %2 = OpLabel 90 // ... 91 // In this example, %2 is not an acceptable successor of %3 since one of the 92 // dependencies (%4) of the propagated instruction (%5) does not dominate it. 93 static std::unordered_set<uint32_t> GetAcceptableSuccessors( 94 opt::IRContext* ir_context, uint32_t block_id); 95 96 std::unordered_set<uint32_t> GetFreshIds() const override; 97 98 private: 99 // Returns the last possible instruction in the |block_id| that satisfies the 100 // following properties: 101 // - has result id 102 // - has type id 103 // - has supported opcode (see IsOpcodeSupported method) 104 // - has no users in its basic block. 105 // Returns nullptr if no such an instruction exists. For example: 106 // %1 = OpLabel 107 // %2 = OpUndef %int 108 // %3 = OpUndef %int 109 // OpStore %var %3 110 // OpBranch %some_block 111 // In this example: 112 // - We cannot propagate OpBranch nor OpStore since they both have unsupported 113 // opcodes and have neither result ids nor type ids. 114 // - We cannot propagate %3 either since it is used by OpStore. 115 // - We can propagate %2 since it satisfies all our conditions. 116 // The basic idea behind this method it to make sure that the returned 117 // instruction will not break domination rules in its original block when 118 // propagated. 119 static opt::Instruction* GetInstructionToPropagate(opt::IRContext* ir_context, 120 uint32_t block_id); 121 122 // Returns true if |opcode| is supported by this transformation. 123 static bool IsOpcodeSupported(SpvOp opcode); 124 125 // Returns the first instruction in the |block| that allows us to insert 126 // |opcode| above itself. Returns nullptr is no such instruction exists. 127 static opt::Instruction* GetFirstInsertBeforeInstruction( 128 opt::IRContext* ir_context, uint32_t block_id, SpvOp opcode); 129 130 // Returns a result id of a basic block, where an OpPhi instruction can be 131 // inserted. Returns nullptr if it's not possible to create an OpPhi. The 132 // created OpPhi instruction groups all the propagated clones of the original 133 // instruction. |block_id| is a result id of the block we propagate the 134 // instruction from. |successor_ids| contains result ids of the successors we 135 // propagate the instruction into. Concretely, returns a non-null value if: 136 // - |block_id| is in some construct. 137 // - The merge block of that construct is reachable. 138 // - |block_id| dominates that merge block. 139 // - That merge block may not be an acceptable successor of |block_id|. 140 // - There must be at least one |block_id|'s acceptable successor for every 141 // predecessor of the merge block, dominating that predecessor. 142 // - We can't create an OpPhi if the module has neither VariablePointers nor 143 // VariablePointersStorageBuffer capabilities. 144 // A simple example of when we can insert an OpPhi instruction is: 145 // - This snippet of code: 146 // %1 = OpLabel 147 // %2 = OpUndef %int 148 // OpSelectionMerge %5 None 149 // OpBranchConditional %cond %3 %4 150 // %3 = OpLabel 151 // OpBranch %5 152 // %4 = OpLabel 153 // OpBranch %5 154 // %5 = OpLabel 155 // ... 156 // will be transformed into the following one (if %2 is propagated): 157 // %1 = OpLabel 158 // OpSelectionMerge %5 None 159 // OpBranchConditional %cond %3 %4 160 // %3 = OpLabel 161 // %6 = OpUndef %int 162 // OpBranch %5 163 // %4 = OpLabel 164 // %7 = OpUndef %int 165 // OpBranch %5 166 // %5 = OpLabel 167 // %8 = OpPhi %int %6 %3 %7 %4 168 // ... 169 // The fact that we introduce an OpPhi allows us to increase the applicability 170 // of the transformation. Concretely, we wouldn't be able to apply it in the 171 // example above if %2 were used in %5. Some more complicated examples can be 172 // found in unit tests. 173 static uint32_t GetOpPhiBlockId( 174 opt::IRContext* ir_context, uint32_t block_id, 175 const opt::Instruction& inst_to_propagate, 176 const std::unordered_set<uint32_t>& successor_ids); 177 178 protobufs::TransformationPropagateInstructionDown message_; 179 }; 180 181 } // namespace fuzz 182 } // namespace spvtools 183 184 #endif // SOURCE_FUZZ_TRANSFORMATION_PROPAGATE_INSTRUCTION_DOWN_H_ 185