aboutsummaryrefslogtreecommitdiff
path: root/source/fuzz/fuzzer_pass_flatten_conditional_branches.cpp
blob: 84da7a749e437e05a28b825c6efca153be7875c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// Copyright (c) 2020 Google LLC
//
// 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.

#include "source/fuzz/fuzzer_pass_flatten_conditional_branches.h"

#include "source/fuzz/comparator_deep_blocks_first.h"
#include "source/fuzz/instruction_descriptor.h"
#include "source/fuzz/transformation_flatten_conditional_branch.h"

namespace spvtools {
namespace fuzz {

// A fuzzer pass that randomly selects conditional branches to flatten and
// flattens them, if possible.
FuzzerPassFlattenConditionalBranches::FuzzerPassFlattenConditionalBranches(
    opt::IRContext* ir_context, TransformationContext* transformation_context,
    FuzzerContext* fuzzer_context,
    protobufs::TransformationSequence* transformations)
    : FuzzerPass(ir_context, transformation_context, fuzzer_context,
                 transformations) {}

void FuzzerPassFlattenConditionalBranches::Apply() {
  for (auto& function : *GetIRContext()->module()) {
    // Get all the selection headers that we want to flatten. We need to collect
    // all of them first, because, since we are changing the structure of the
    // module, it's not safe to modify them while iterating.
    std::vector<opt::BasicBlock*> selection_headers;

    for (auto& block : function) {
      // Randomly decide whether to consider this block.
      if (!GetFuzzerContext()->ChoosePercentage(
              GetFuzzerContext()->GetChanceOfFlatteningConditionalBranch())) {
        continue;
      }

      // Only consider this block if it is the header of a conditional, with a
      // non-irrelevant condition.
      if (block.GetMergeInst() &&
          block.GetMergeInst()->opcode() == SpvOpSelectionMerge &&
          block.terminator()->opcode() == SpvOpBranchConditional &&
          !GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
              block.terminator()->GetSingleWordInOperand(0))) {
        selection_headers.emplace_back(&block);
      }
    }

    // Sort the headers so that those that are more deeply nested are considered
    // first, possibly enabling outer conditionals to be flattened.
    std::sort(selection_headers.begin(), selection_headers.end(),
              ComparatorDeepBlocksFirst(GetIRContext()));

    // Apply the transformation to the headers which can be flattened.
    for (auto header : selection_headers) {
      // Make a set to keep track of the instructions that need fresh ids.
      std::set<opt::Instruction*> instructions_that_need_ids;

      // Do not consider this header if the conditional cannot be flattened.
      if (!TransformationFlattenConditionalBranch::
              GetProblematicInstructionsIfConditionalCanBeFlattened(
                  GetIRContext(), header, *GetTransformationContext(),
                  &instructions_that_need_ids)) {
        continue;
      }

      uint32_t convergence_block_id =
          TransformationFlattenConditionalBranch::FindConvergenceBlock(
              GetIRContext(), *header);

      // If the SPIR-V version is restricted so that OpSelect can only work on
      // scalar, pointer and vector types then we cannot apply this
      // transformation to a header whose convergence block features OpPhi
      // instructions on different types, as we cannot convert such instructions
      // to OpSelect instructions.
      if (TransformationFlattenConditionalBranch::
              OpSelectArgumentsAreRestricted(GetIRContext())) {
        if (!GetIRContext()
                 ->cfg()
                 ->block(convergence_block_id)
                 ->WhileEachPhiInst(
                     [this](opt::Instruction* phi_instruction) -> bool {
                       switch (GetIRContext()
                                   ->get_def_use_mgr()
                                   ->GetDef(phi_instruction->type_id())
                                   ->opcode()) {
                         case SpvOpTypeBool:
                         case SpvOpTypeInt:
                         case SpvOpTypeFloat:
                         case SpvOpTypePointer:
                         case SpvOpTypeVector:
                           return true;
                         default:
                           return false;
                       }
                     })) {
          // An OpPhi is performed on a type not supported by OpSelect; we
          // cannot flatten this selection.
          continue;
        }
      }

      // If the construct's convergence block features OpPhi instructions with
      // vector result types then we may be *forced*, by the SPIR-V version, to
      // turn these into component-wise OpSelect instructions, or we might wish
      // to do so anyway.  The following booleans capture whether we will opt
      // to use a component-wise select even if we don't have to.
      bool use_component_wise_2d_select_even_if_optional =
          GetFuzzerContext()->ChooseEven();
      bool use_component_wise_3d_select_even_if_optional =
          GetFuzzerContext()->ChooseEven();
      bool use_component_wise_4d_select_even_if_optional =
          GetFuzzerContext()->ChooseEven();

      // If we do need to perform any component-wise selections, we will need a
      // fresh id for a boolean vector representing the selection's condition
      // repeated N times, where N is the vector dimension.
      uint32_t fresh_id_for_bvec2_selector = 0;
      uint32_t fresh_id_for_bvec3_selector = 0;
      uint32_t fresh_id_for_bvec4_selector = 0;

      GetIRContext()
          ->cfg()
          ->block(convergence_block_id)
          ->ForEachPhiInst([this, &fresh_id_for_bvec2_selector,
                            &fresh_id_for_bvec3_selector,
                            &fresh_id_for_bvec4_selector,
                            use_component_wise_2d_select_even_if_optional,
                            use_component_wise_3d_select_even_if_optional,
                            use_component_wise_4d_select_even_if_optional](
                               opt::Instruction* phi_instruction) {
            opt::Instruction* type_instruction =
                GetIRContext()->get_def_use_mgr()->GetDef(
                    phi_instruction->type_id());
            switch (type_instruction->opcode()) {
              case SpvOpTypeVector: {
                uint32_t dimension =
                    type_instruction->GetSingleWordInOperand(1);
                switch (dimension) {
                  case 2:
                    PrepareForOpPhiOnVectors(
                        dimension,
                        use_component_wise_2d_select_even_if_optional,
                        &fresh_id_for_bvec2_selector);
                    break;
                  case 3:
                    PrepareForOpPhiOnVectors(
                        dimension,
                        use_component_wise_3d_select_even_if_optional,
                        &fresh_id_for_bvec3_selector);
                    break;
                  case 4:
                    PrepareForOpPhiOnVectors(
                        dimension,
                        use_component_wise_4d_select_even_if_optional,
                        &fresh_id_for_bvec4_selector);
                    break;
                  default:
                    assert(false && "Invalid vector dimension.");
                }
                break;
              }
              default:
                break;
            }
          });

      // Some instructions will require to be enclosed inside conditionals
      // because they have side effects (for example, loads and stores). Some of
      // this have no result id, so we require instruction descriptors to
      // identify them. Each of them is associated with the necessary ids for it
      // via a SideEffectWrapperInfo message.
      std::vector<protobufs::SideEffectWrapperInfo> wrappers_info;

      for (auto instruction : instructions_that_need_ids) {
        protobufs::SideEffectWrapperInfo wrapper_info;
        *wrapper_info.mutable_instruction() =
            MakeInstructionDescriptor(GetIRContext(), instruction);
        wrapper_info.set_merge_block_id(GetFuzzerContext()->GetFreshId());
        wrapper_info.set_execute_block_id(GetFuzzerContext()->GetFreshId());

        // If the instruction has a non-void result id, we need to define more
        // fresh ids and provide an id of the suitable type whose value can be
        // copied in order to create a placeholder id.
        if (TransformationFlattenConditionalBranch::InstructionNeedsPlaceholder(
                GetIRContext(), *instruction)) {
          wrapper_info.set_actual_result_id(GetFuzzerContext()->GetFreshId());
          wrapper_info.set_alternative_block_id(
              GetFuzzerContext()->GetFreshId());
          wrapper_info.set_placeholder_result_id(
              GetFuzzerContext()->GetFreshId());

          // The id will be a zero constant if the type allows it, and an
          // OpUndef otherwise. We want to avoid using OpUndef, if possible, to
          // avoid undefined behaviour in the module as much as possible.
          if (fuzzerutil::CanCreateConstant(GetIRContext(),
                                            instruction->type_id())) {
            wrapper_info.set_value_to_copy_id(
                FindOrCreateZeroConstant(instruction->type_id(), true));
          } else {
            wrapper_info.set_value_to_copy_id(
                FindOrCreateGlobalUndef(instruction->type_id()));
          }
        }

        wrappers_info.push_back(std::move(wrapper_info));
      }

      // Apply the transformation, evenly choosing whether to lay out the true
      // branch or the false branch first.
      ApplyTransformation(TransformationFlattenConditionalBranch(
          header->id(), GetFuzzerContext()->ChooseEven(),
          fresh_id_for_bvec2_selector, fresh_id_for_bvec3_selector,
          fresh_id_for_bvec4_selector, wrappers_info));
    }
  }
}

void FuzzerPassFlattenConditionalBranches::PrepareForOpPhiOnVectors(
    uint32_t vector_dimension, bool use_vector_select_if_optional,
    uint32_t* fresh_id_for_bvec_selector) {
  if (*fresh_id_for_bvec_selector != 0) {
    // We already have a fresh id for a component-wise OpSelect of this
    // dimension
    return;
  }
  if (TransformationFlattenConditionalBranch::OpSelectArgumentsAreRestricted(
          GetIRContext()) ||
      use_vector_select_if_optional) {
    // We either have to, or have chosen to, perform a component-wise select, so
    // we ensure that the right boolean vector type is available, and grab a
    // fresh id.
    FindOrCreateVectorType(FindOrCreateBoolType(), vector_dimension);
    *fresh_id_for_bvec_selector = GetFuzzerContext()->GetFreshId();
  }
}

}  // namespace fuzz
}  // namespace spvtools