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