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 #include "source/fuzz/transformation_wrap_region_in_selection.h"
16 
17 #include "source/fuzz/fuzzer_util.h"
18 
19 namespace spvtools {
20 namespace fuzz {
21 
TransformationWrapRegionInSelection(const protobufs::TransformationWrapRegionInSelection & message)22 TransformationWrapRegionInSelection::TransformationWrapRegionInSelection(
23     const protobufs::TransformationWrapRegionInSelection& message)
24     : message_(message) {}
25 
TransformationWrapRegionInSelection(uint32_t region_entry_block_id,uint32_t region_exit_block_id,bool branch_condition)26 TransformationWrapRegionInSelection::TransformationWrapRegionInSelection(
27     uint32_t region_entry_block_id, uint32_t region_exit_block_id,
28     bool branch_condition) {
29   message_.set_region_entry_block_id(region_entry_block_id);
30   message_.set_region_exit_block_id(region_exit_block_id);
31   message_.set_branch_condition(branch_condition);
32 }
33 
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const34 bool TransformationWrapRegionInSelection::IsApplicable(
35     opt::IRContext* ir_context,
36     const TransformationContext& transformation_context) const {
37   // Check that it is possible to outline a region of blocks without breaking
38   // domination and structured control flow rules.
39   if (!IsApplicableToBlockRange(ir_context, message_.region_entry_block_id(),
40                                 message_.region_exit_block_id())) {
41     return false;
42   }
43 
44   // There must exist an irrelevant boolean constant to be used as a condition
45   // in the OpBranchConditional instruction.
46   return fuzzerutil::MaybeGetBoolConstant(ir_context, transformation_context,
47                                           message_.branch_condition(),
48                                           true) != 0;
49 }
50 
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const51 void TransformationWrapRegionInSelection::Apply(
52     opt::IRContext* ir_context,
53     TransformationContext* transformation_context) const {
54   auto* new_header_block =
55       ir_context->cfg()->block(message_.region_entry_block_id());
56   assert(new_header_block->terminator()->opcode() == SpvOpBranch &&
57          "This condition should have been checked in the IsApplicable");
58 
59   const auto successor_id =
60       new_header_block->terminator()->GetSingleWordInOperand(0);
61 
62   // Change |entry_block|'s terminator to |OpBranchConditional|.
63   new_header_block->terminator()->SetOpcode(SpvOpBranchConditional);
64   new_header_block->terminator()->SetInOperands(
65       {{SPV_OPERAND_TYPE_ID,
66         {fuzzerutil::MaybeGetBoolConstant(ir_context, *transformation_context,
67                                           message_.branch_condition(), true)}},
68        {SPV_OPERAND_TYPE_ID, {successor_id}},
69        {SPV_OPERAND_TYPE_ID, {successor_id}}});
70 
71   // Insert OpSelectionMerge before the terminator.
72   new_header_block->terminator()->InsertBefore(MakeUnique<opt::Instruction>(
73       ir_context, SpvOpSelectionMerge, 0, 0,
74       opt::Instruction::OperandList{
75           {SPV_OPERAND_TYPE_ID, {message_.region_exit_block_id()}},
76           {SPV_OPERAND_TYPE_SELECTION_CONTROL,
77            {SpvSelectionControlMaskNone}}}));
78 
79   // We've change the module so we must invalidate analyses.
80   ir_context->InvalidateAnalysesExceptFor(opt::IRContext::kAnalysisNone);
81 }
82 
ToMessage() const83 protobufs::Transformation TransformationWrapRegionInSelection::ToMessage()
84     const {
85   protobufs::Transformation result;
86   *result.mutable_wrap_region_in_selection() = message_;
87   return result;
88 }
89 
IsApplicableToBlockRange(opt::IRContext * ir_context,uint32_t header_block_candidate_id,uint32_t merge_block_candidate_id)90 bool TransformationWrapRegionInSelection::IsApplicableToBlockRange(
91     opt::IRContext* ir_context, uint32_t header_block_candidate_id,
92     uint32_t merge_block_candidate_id) {
93   // Check that |header_block_candidate_id| and |merge_block_candidate_id| are
94   // valid.
95   const auto* header_block_candidate =
96       fuzzerutil::MaybeFindBlock(ir_context, header_block_candidate_id);
97   if (!header_block_candidate) {
98     return false;
99   }
100 
101   const auto* merge_block_candidate =
102       fuzzerutil::MaybeFindBlock(ir_context, merge_block_candidate_id);
103   if (!merge_block_candidate) {
104     return false;
105   }
106 
107   // |header_block_candidate| and |merge_block_candidate| must be from the same
108   // function.
109   if (header_block_candidate->GetParent() !=
110       merge_block_candidate->GetParent()) {
111     return false;
112   }
113 
114   const auto* dominator_analysis =
115       ir_context->GetDominatorAnalysis(header_block_candidate->GetParent());
116   const auto* postdominator_analysis =
117       ir_context->GetPostDominatorAnalysis(header_block_candidate->GetParent());
118 
119   if (!dominator_analysis->StrictlyDominates(header_block_candidate,
120                                              merge_block_candidate) ||
121       !postdominator_analysis->StrictlyDominates(merge_block_candidate,
122                                                  header_block_candidate)) {
123     return false;
124   }
125 
126   // |header_block_candidate| can't be a header since we are about to make it
127   // one.
128   if (header_block_candidate->GetMergeInst()) {
129     return false;
130   }
131 
132   // |header_block_candidate| must have an OpBranch terminator.
133   if (header_block_candidate->terminator()->opcode() != SpvOpBranch) {
134     return false;
135   }
136 
137   // Every header block must have a unique merge block. Thus,
138   // |merge_block_candidate| can't be a merge block of some other header.
139   auto* structured_cfg = ir_context->GetStructuredCFGAnalysis();
140   if (structured_cfg->IsMergeBlock(merge_block_candidate_id)) {
141     return false;
142   }
143 
144   // |header_block_candidate|'s containing construct must also contain
145   // |merge_block_candidate|.
146   //
147   // ContainingConstruct will return the id of a loop header for a block in the
148   // loop's continue construct. Thus, we must also check the case when one of
149   // the candidates is in continue construct and the other one is not.
150   if (structured_cfg->ContainingConstruct(header_block_candidate_id) !=
151           structured_cfg->ContainingConstruct(merge_block_candidate_id) ||
152       structured_cfg->IsInContinueConstruct(header_block_candidate_id) !=
153           structured_cfg->IsInContinueConstruct(merge_block_candidate_id)) {
154     return false;
155   }
156 
157   return true;
158 }
159 
GetFreshIds() const160 std::unordered_set<uint32_t> TransformationWrapRegionInSelection::GetFreshIds()
161     const {
162   return std::unordered_set<uint32_t>();
163 }
164 
165 }  // namespace fuzz
166 }  // namespace spvtools
167