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_add_parameter.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18
19 namespace spvtools {
20 namespace fuzz {
21
TransformationAddParameter(protobufs::TransformationAddParameter message)22 TransformationAddParameter::TransformationAddParameter(
23 protobufs::TransformationAddParameter message)
24 : message_(std::move(message)) {}
25
TransformationAddParameter(uint32_t function_id,uint32_t parameter_fresh_id,uint32_t parameter_type_id,std::map<uint32_t,uint32_t> call_parameter_ids,uint32_t function_type_fresh_id)26 TransformationAddParameter::TransformationAddParameter(
27 uint32_t function_id, uint32_t parameter_fresh_id,
28 uint32_t parameter_type_id, std::map<uint32_t, uint32_t> call_parameter_ids,
29 uint32_t function_type_fresh_id) {
30 message_.set_function_id(function_id);
31 message_.set_parameter_fresh_id(parameter_fresh_id);
32 message_.set_parameter_type_id(parameter_type_id);
33 *message_.mutable_call_parameter_ids() =
34 fuzzerutil::MapToRepeatedUInt32Pair(call_parameter_ids);
35 message_.set_function_type_fresh_id(function_type_fresh_id);
36 }
37
IsApplicable(opt::IRContext * ir_context,const TransformationContext &) const38 bool TransformationAddParameter::IsApplicable(
39 opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
40 // Check that function exists.
41 const auto* function =
42 fuzzerutil::FindFunction(ir_context, message_.function_id());
43 if (!function ||
44 fuzzerutil::FunctionIsEntryPoint(ir_context, function->result_id())) {
45 return false;
46 }
47
48 // The type must be supported.
49 if (ir_context->get_def_use_mgr()->GetDef(message_.parameter_type_id()) ==
50 nullptr) {
51 return false;
52 }
53 if (!IsParameterTypeSupported(ir_context, message_.parameter_type_id())) {
54 return false;
55 }
56
57 // Iterate over all callers.
58 std::map<uint32_t, uint32_t> call_parameter_ids_map =
59 fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids());
60 for (auto* instr :
61 fuzzerutil::GetCallers(ir_context, message_.function_id())) {
62 uint32_t caller_id = instr->result_id();
63
64 // If there is no entry for this caller, return false.
65 if (call_parameter_ids_map.find(caller_id) ==
66 call_parameter_ids_map.end()) {
67 return false;
68 }
69 uint32_t value_id = call_parameter_ids_map[caller_id];
70
71 auto value_instr = ir_context->get_def_use_mgr()->GetDef(value_id);
72 if (!value_instr) {
73 return false;
74 }
75 // If the id of the value of the map is not available before the caller,
76 // return false.
77 if (!fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, instr,
78 value_id)) {
79 return false;
80 }
81
82 // The type of the value must be defined.
83 uint32_t value_type_id = fuzzerutil::GetTypeId(ir_context, value_id);
84 if (!value_type_id) {
85 return false;
86 }
87
88 // Type of every value of the map must be the same for all callers.
89 if (message_.parameter_type_id() != value_type_id) {
90 return false;
91 }
92 }
93 return fuzzerutil::IsFreshId(ir_context, message_.parameter_fresh_id()) &&
94 fuzzerutil::IsFreshId(ir_context, message_.function_type_fresh_id()) &&
95 message_.parameter_fresh_id() != message_.function_type_fresh_id();
96 }
97
Apply(opt::IRContext * ir_context,TransformationContext * transformation_context) const98 void TransformationAddParameter::Apply(
99 opt::IRContext* ir_context,
100 TransformationContext* transformation_context) const {
101 // Find the function that will be transformed.
102 auto* function = fuzzerutil::FindFunction(ir_context, message_.function_id());
103 assert(function && "Can't find the function");
104
105 std::map<uint32_t, uint32_t> call_parameter_ids_map =
106 fuzzerutil::RepeatedUInt32PairToMap(message_.call_parameter_ids());
107
108 uint32_t new_parameter_type_id = message_.parameter_type_id();
109 auto new_parameter_type =
110 ir_context->get_type_mgr()->GetType(new_parameter_type_id);
111 assert(new_parameter_type && "New parameter has invalid type.");
112
113 // Add new parameters to the function.
114 function->AddParameter(MakeUnique<opt::Instruction>(
115 ir_context, SpvOpFunctionParameter, new_parameter_type_id,
116 message_.parameter_fresh_id(), opt::Instruction::OperandList()));
117
118 fuzzerutil::UpdateModuleIdBound(ir_context, message_.parameter_fresh_id());
119
120 // Fix all OpFunctionCall instructions.
121 for (auto* inst : fuzzerutil::GetCallers(ir_context, function->result_id())) {
122 inst->AddOperand(
123 {SPV_OPERAND_TYPE_ID, {call_parameter_ids_map[inst->result_id()]}});
124 }
125
126 // Update function's type.
127 {
128 // We use a separate scope here since |old_function_type| might become a
129 // dangling pointer after the call to the fuzzerutil::UpdateFunctionType.
130
131 const auto* old_function_type =
132 fuzzerutil::GetFunctionType(ir_context, function);
133 assert(old_function_type && "Function must have a valid type");
134
135 std::vector<uint32_t> parameter_type_ids;
136 for (uint32_t i = 1; i < old_function_type->NumInOperands(); ++i) {
137 parameter_type_ids.push_back(
138 old_function_type->GetSingleWordInOperand(i));
139 }
140
141 parameter_type_ids.push_back(new_parameter_type_id);
142
143 fuzzerutil::UpdateFunctionType(
144 ir_context, function->result_id(), message_.function_type_fresh_id(),
145 old_function_type->GetSingleWordInOperand(0), parameter_type_ids);
146 }
147
148 auto new_parameter_kind = new_parameter_type->kind();
149
150 // Make sure our changes are analyzed.
151 ir_context->InvalidateAnalysesExceptFor(
152 opt::IRContext::Analysis::kAnalysisNone);
153
154 // If the |new_parameter_type_id| is not a pointer type, mark id as
155 // irrelevant so that we can replace its use with some other id. If the
156 // |new_parameter_type_id| is a pointer type, we cannot mark it with
157 // IdIsIrrelevant, because this pointer might be replaced by a pointer from
158 // original shader. This would change the semantics of the module. In the case
159 // of a pointer type we mark it with PointeeValueIsIrrelevant.
160 if (new_parameter_kind != opt::analysis::Type::kPointer) {
161 transformation_context->GetFactManager()->AddFactIdIsIrrelevant(
162 message_.parameter_fresh_id());
163 } else {
164 transformation_context->GetFactManager()->AddFactValueOfPointeeIsIrrelevant(
165 message_.parameter_fresh_id());
166 }
167 }
168
ToMessage() const169 protobufs::Transformation TransformationAddParameter::ToMessage() const {
170 protobufs::Transformation result;
171 *result.mutable_add_parameter() = message_;
172 return result;
173 }
174
IsParameterTypeSupported(opt::IRContext * ir_context,uint32_t type_id)175 bool TransformationAddParameter::IsParameterTypeSupported(
176 opt::IRContext* ir_context, uint32_t type_id) {
177 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3403):
178 // Think about other type instructions we can add here.
179 opt::Instruction* type_inst = ir_context->get_def_use_mgr()->GetDef(type_id);
180 switch (type_inst->opcode()) {
181 case SpvOpTypeBool:
182 case SpvOpTypeInt:
183 case SpvOpTypeFloat:
184 case SpvOpTypeMatrix:
185 case SpvOpTypeVector:
186 return true;
187 case SpvOpTypeArray:
188 return IsParameterTypeSupported(ir_context,
189 type_inst->GetSingleWordInOperand(0));
190 case SpvOpTypeStruct:
191 if (fuzzerutil::HasBlockOrBufferBlockDecoration(ir_context, type_id)) {
192 return false;
193 }
194 for (uint32_t i = 0; i < type_inst->NumInOperands(); i++) {
195 if (!IsParameterTypeSupported(ir_context,
196 type_inst->GetSingleWordInOperand(i))) {
197 return false;
198 }
199 }
200 return true;
201 case SpvOpTypePointer: {
202 SpvStorageClass storage_class =
203 static_cast<SpvStorageClass>(type_inst->GetSingleWordInOperand(0));
204 switch (storage_class) {
205 case SpvStorageClassPrivate:
206 case SpvStorageClassFunction:
207 case SpvStorageClassWorkgroup: {
208 return IsParameterTypeSupported(ir_context,
209 type_inst->GetSingleWordInOperand(1));
210 }
211 default:
212 return false;
213 }
214 }
215 default:
216 return false;
217 }
218 }
219
GetFreshIds() const220 std::unordered_set<uint32_t> TransformationAddParameter::GetFreshIds() const {
221 return {message_.parameter_fresh_id(), message_.function_type_fresh_id()};
222 }
223
224 } // namespace fuzz
225 } // namespace spvtools
226