1 // Copyright (c) 2019 Google LLC
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/fuzzer_pass_apply_id_synonyms.h"
16
17 #include "source/fuzz/data_descriptor.h"
18 #include "source/fuzz/fuzzer_util.h"
19 #include "source/fuzz/id_use_descriptor.h"
20 #include "source/fuzz/instruction_descriptor.h"
21 #include "source/fuzz/transformation_composite_extract.h"
22 #include "source/fuzz/transformation_compute_data_synonym_fact_closure.h"
23 #include "source/fuzz/transformation_replace_id_with_synonym.h"
24
25 namespace spvtools {
26 namespace fuzz {
27
FuzzerPassApplyIdSynonyms(opt::IRContext * ir_context,TransformationContext * transformation_context,FuzzerContext * fuzzer_context,protobufs::TransformationSequence * transformations,bool ignore_inapplicable_transformations)28 FuzzerPassApplyIdSynonyms::FuzzerPassApplyIdSynonyms(
29 opt::IRContext* ir_context, TransformationContext* transformation_context,
30 FuzzerContext* fuzzer_context,
31 protobufs::TransformationSequence* transformations,
32 bool ignore_inapplicable_transformations)
33 : FuzzerPass(ir_context, transformation_context, fuzzer_context,
34 transformations, ignore_inapplicable_transformations) {}
35
Apply()36 void FuzzerPassApplyIdSynonyms::Apply() {
37 // Compute a closure of data synonym facts, to enrich the pool of synonyms
38 // that are available.
39 ApplyTransformation(TransformationComputeDataSynonymFactClosure(
40 GetFuzzerContext()
41 ->GetMaximumEquivalenceClassSizeForDataSynonymFactClosure()));
42
43 for (auto id_with_known_synonyms : GetTransformationContext()
44 ->GetFactManager()
45 ->GetIdsForWhichSynonymsAreKnown()) {
46 // Gather up all uses of |id_with_known_synonym| as a regular id, and
47 // subsequently iterate over these uses. We use this separation because,
48 // when considering a given use, we might apply a transformation that will
49 // invalidate the def-use manager.
50 std::vector<std::pair<opt::Instruction*, uint32_t>> uses;
51 GetIRContext()->get_def_use_mgr()->ForEachUse(
52 id_with_known_synonyms,
53 [&uses](opt::Instruction* use_inst, uint32_t use_index) -> void {
54 // We only gather up regular id uses; e.g. we do not include a use of
55 // the id as the scope for an atomic operation.
56 if (use_inst->GetOperand(use_index).type == SPV_OPERAND_TYPE_ID) {
57 uses.emplace_back(
58 std::pair<opt::Instruction*, uint32_t>(use_inst, use_index));
59 }
60 });
61
62 for (const auto& use : uses) {
63 auto use_inst = use.first;
64 auto use_index = use.second;
65 auto block_containing_use = GetIRContext()->get_instr_block(use_inst);
66 // The use might not be in a block; e.g. it could be a decoration.
67 if (!block_containing_use) {
68 continue;
69 }
70 if (!GetFuzzerContext()->ChoosePercentage(
71 GetFuzzerContext()->GetChanceOfReplacingIdWithSynonym())) {
72 continue;
73 }
74 // |use_index| is the absolute index of the operand. We require
75 // the index of the operand restricted to input operands only.
76 uint32_t use_in_operand_index =
77 fuzzerutil::InOperandIndexFromOperandIndex(*use_inst, use_index);
78 if (!fuzzerutil::IdUseCanBeReplaced(GetIRContext(),
79 *GetTransformationContext(), use_inst,
80 use_in_operand_index)) {
81 continue;
82 }
83
84 std::vector<const protobufs::DataDescriptor*> synonyms_to_try;
85 for (const auto* data_descriptor :
86 GetTransformationContext()->GetFactManager()->GetSynonymsForId(
87 id_with_known_synonyms)) {
88 protobufs::DataDescriptor descriptor_for_this_id =
89 MakeDataDescriptor(id_with_known_synonyms, {});
90 if (DataDescriptorEquals()(data_descriptor, &descriptor_for_this_id)) {
91 // Exclude the fact that the id is synonymous with itself.
92 continue;
93 }
94
95 if (DataDescriptorsHaveCompatibleTypes(
96 use_inst->opcode(), use_in_operand_index,
97 descriptor_for_this_id, *data_descriptor)) {
98 synonyms_to_try.push_back(data_descriptor);
99 }
100 }
101 while (!synonyms_to_try.empty()) {
102 auto synonym_to_try =
103 GetFuzzerContext()->RemoveAtRandomIndex(&synonyms_to_try);
104
105 // If the synonym's |index_size| is zero, the synonym represents an id.
106 // Otherwise it represents some element of a composite structure, in
107 // which case we need to be able to add an extract instruction to get
108 // that element out.
109 if (synonym_to_try->index_size() > 0 &&
110 !fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpCompositeExtract,
111 use_inst) &&
112 use_inst->opcode() != SpvOpPhi) {
113 // We cannot insert an extract before this instruction, so this
114 // synonym is no good.
115 continue;
116 }
117
118 if (!fuzzerutil::IdIsAvailableAtUse(GetIRContext(), use_inst,
119 use_in_operand_index,
120 synonym_to_try->object())) {
121 continue;
122 }
123
124 // We either replace the use with an id known to be synonymous (when
125 // the synonym's |index_size| is 0), or an id that will hold the result
126 // of extracting a synonym from a composite (when the synonym's
127 // |index_size| is > 0).
128 uint32_t id_with_which_to_replace_use;
129 if (synonym_to_try->index_size() == 0) {
130 id_with_which_to_replace_use = synonym_to_try->object();
131 } else {
132 id_with_which_to_replace_use = GetFuzzerContext()->GetFreshId();
133 opt::Instruction* instruction_to_insert_before = nullptr;
134
135 if (use_inst->opcode() != SpvOpPhi) {
136 instruction_to_insert_before = use_inst;
137 } else {
138 auto parent_block_id =
139 use_inst->GetSingleWordInOperand(use_in_operand_index + 1);
140 auto parent_block_instruction =
141 GetIRContext()->get_def_use_mgr()->GetDef(parent_block_id);
142 auto parent_block =
143 GetIRContext()->get_instr_block(parent_block_instruction);
144
145 instruction_to_insert_before = parent_block->GetMergeInst()
146 ? parent_block->GetMergeInst()
147 : parent_block->terminator();
148 }
149
150 if (GetTransformationContext()->GetFactManager()->BlockIsDead(
151 GetIRContext()
152 ->get_instr_block(instruction_to_insert_before)
153 ->id())) {
154 // We cannot create a synonym via a composite extraction in a dead
155 // block, as the resulting id is irrelevant.
156 continue;
157 }
158
159 assert(!GetTransformationContext()->GetFactManager()->IdIsIrrelevant(
160 synonym_to_try->object()) &&
161 "Irrelevant ids can't participate in DataSynonym facts");
162 ApplyTransformation(TransformationCompositeExtract(
163 MakeInstructionDescriptor(GetIRContext(),
164 instruction_to_insert_before),
165 id_with_which_to_replace_use, synonym_to_try->object(),
166 fuzzerutil::RepeatedFieldToVector(synonym_to_try->index())));
167 assert(GetTransformationContext()->GetFactManager()->IsSynonymous(
168 MakeDataDescriptor(id_with_which_to_replace_use, {}),
169 *synonym_to_try) &&
170 "The extracted id must be synonymous with the component from "
171 "which it was extracted.");
172 }
173
174 ApplyTransformation(TransformationReplaceIdWithSynonym(
175 MakeIdUseDescriptorFromUse(GetIRContext(), use_inst,
176 use_in_operand_index),
177 id_with_which_to_replace_use));
178 break;
179 }
180 }
181 }
182 }
183
DataDescriptorsHaveCompatibleTypes(SpvOp opcode,uint32_t use_in_operand_index,const protobufs::DataDescriptor & dd1,const protobufs::DataDescriptor & dd2)184 bool FuzzerPassApplyIdSynonyms::DataDescriptorsHaveCompatibleTypes(
185 SpvOp opcode, uint32_t use_in_operand_index,
186 const protobufs::DataDescriptor& dd1,
187 const protobufs::DataDescriptor& dd2) {
188 auto base_object_type_id_1 =
189 fuzzerutil::GetTypeId(GetIRContext(), dd1.object());
190 auto base_object_type_id_2 =
191 fuzzerutil::GetTypeId(GetIRContext(), dd2.object());
192 assert(base_object_type_id_1 && base_object_type_id_2 &&
193 "Data descriptors are invalid");
194
195 auto type_id_1 = fuzzerutil::WalkCompositeTypeIndices(
196 GetIRContext(), base_object_type_id_1, dd1.index());
197 auto type_id_2 = fuzzerutil::WalkCompositeTypeIndices(
198 GetIRContext(), base_object_type_id_2, dd2.index());
199 assert(type_id_1 && type_id_2 && "Data descriptors have invalid types");
200
201 return fuzzerutil::TypesAreCompatible(
202 GetIRContext(), opcode, use_in_operand_index, type_id_1, type_id_2);
203 }
204
205 } // namespace fuzz
206 } // namespace spvtools
207