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/transformation_replace_boolean_constant_with_constant_binary.h"
16
17 #include <cmath>
18
19 #include "source/fuzz/fuzzer_util.h"
20 #include "source/fuzz/id_use_descriptor.h"
21
22 namespace spvtools {
23 namespace fuzz {
24
25 namespace {
26
27 // Given floating-point values |lhs| and |rhs|, and a floating-point binary
28 // operator |binop|, returns true if it is certain that 'lhs binop rhs'
29 // evaluates to |required_value|.
30 template <typename T>
float_binop_evaluates_to(T lhs,T rhs,SpvOp binop,bool required_value)31 bool float_binop_evaluates_to(T lhs, T rhs, SpvOp binop, bool required_value) {
32 // Infinity and NaN values are conservatively treated as out of scope.
33 if (!std::isfinite(lhs) || !std::isfinite(rhs)) {
34 return false;
35 }
36 bool binop_result;
37 // The following captures the binary operators that spirv-fuzz can actually
38 // generate when turning a boolean constant into a binary expression.
39 switch (binop) {
40 case SpvOpFOrdGreaterThanEqual:
41 case SpvOpFUnordGreaterThanEqual:
42 binop_result = (lhs >= rhs);
43 break;
44 case SpvOpFOrdGreaterThan:
45 case SpvOpFUnordGreaterThan:
46 binop_result = (lhs > rhs);
47 break;
48 case SpvOpFOrdLessThanEqual:
49 case SpvOpFUnordLessThanEqual:
50 binop_result = (lhs <= rhs);
51 break;
52 case SpvOpFOrdLessThan:
53 case SpvOpFUnordLessThan:
54 binop_result = (lhs < rhs);
55 break;
56 default:
57 return false;
58 }
59 return binop_result == required_value;
60 }
61
62 // Analogous to 'float_binop_evaluates_to', but for signed int values.
63 template <typename T>
signed_int_binop_evaluates_to(T lhs,T rhs,SpvOp binop,bool required_value)64 bool signed_int_binop_evaluates_to(T lhs, T rhs, SpvOp binop,
65 bool required_value) {
66 bool binop_result;
67 switch (binop) {
68 case SpvOpSGreaterThanEqual:
69 binop_result = (lhs >= rhs);
70 break;
71 case SpvOpSGreaterThan:
72 binop_result = (lhs > rhs);
73 break;
74 case SpvOpSLessThanEqual:
75 binop_result = (lhs <= rhs);
76 break;
77 case SpvOpSLessThan:
78 binop_result = (lhs < rhs);
79 break;
80 default:
81 return false;
82 }
83 return binop_result == required_value;
84 }
85
86 // Analogous to 'float_binop_evaluates_to', but for unsigned int values.
87 template <typename T>
unsigned_int_binop_evaluates_to(T lhs,T rhs,SpvOp binop,bool required_value)88 bool unsigned_int_binop_evaluates_to(T lhs, T rhs, SpvOp binop,
89 bool required_value) {
90 bool binop_result;
91 switch (binop) {
92 case SpvOpUGreaterThanEqual:
93 binop_result = (lhs >= rhs);
94 break;
95 case SpvOpUGreaterThan:
96 binop_result = (lhs > rhs);
97 break;
98 case SpvOpULessThanEqual:
99 binop_result = (lhs <= rhs);
100 break;
101 case SpvOpULessThan:
102 binop_result = (lhs < rhs);
103 break;
104 default:
105 return false;
106 }
107 return binop_result == required_value;
108 }
109
110 } // namespace
111
112 TransformationReplaceBooleanConstantWithConstantBinary::
TransformationReplaceBooleanConstantWithConstantBinary(const spvtools::fuzz::protobufs::TransformationReplaceBooleanConstantWithConstantBinary & message)113 TransformationReplaceBooleanConstantWithConstantBinary(
114 const spvtools::fuzz::protobufs::
115 TransformationReplaceBooleanConstantWithConstantBinary& message)
116 : message_(message) {}
117
118 TransformationReplaceBooleanConstantWithConstantBinary::
TransformationReplaceBooleanConstantWithConstantBinary(const protobufs::IdUseDescriptor & id_use_descriptor,uint32_t lhs_id,uint32_t rhs_id,SpvOp comparison_opcode,uint32_t fresh_id_for_binary_operation)119 TransformationReplaceBooleanConstantWithConstantBinary(
120 const protobufs::IdUseDescriptor& id_use_descriptor, uint32_t lhs_id,
121 uint32_t rhs_id, SpvOp comparison_opcode,
122 uint32_t fresh_id_for_binary_operation) {
123 *message_.mutable_id_use_descriptor() = id_use_descriptor;
124 message_.set_lhs_id(lhs_id);
125 message_.set_rhs_id(rhs_id);
126 message_.set_opcode(comparison_opcode);
127 message_.set_fresh_id_for_binary_operation(fresh_id_for_binary_operation);
128 }
129
IsApplicable(opt::IRContext * context,const FactManager &) const130 bool TransformationReplaceBooleanConstantWithConstantBinary::IsApplicable(
131 opt::IRContext* context, const FactManager& /*unused*/) const {
132 // The id for the binary result must be fresh
133 if (!fuzzerutil::IsFreshId(context,
134 message_.fresh_id_for_binary_operation())) {
135 return false;
136 }
137
138 // The used id must be for a boolean constant
139 auto boolean_constant = context->get_def_use_mgr()->GetDef(
140 message_.id_use_descriptor().id_of_interest());
141 if (!boolean_constant) {
142 return false;
143 }
144 if (!(boolean_constant->opcode() == SpvOpConstantFalse ||
145 boolean_constant->opcode() == SpvOpConstantTrue)) {
146 return false;
147 }
148
149 // The left-hand-side id must correspond to a constant instruction.
150 auto lhs_constant_inst =
151 context->get_def_use_mgr()->GetDef(message_.lhs_id());
152 if (!lhs_constant_inst) {
153 return false;
154 }
155 if (lhs_constant_inst->opcode() != SpvOpConstant) {
156 return false;
157 }
158
159 // The right-hand-side id must correspond to a constant instruction.
160 auto rhs_constant_inst =
161 context->get_def_use_mgr()->GetDef(message_.rhs_id());
162 if (!rhs_constant_inst) {
163 return false;
164 }
165 if (rhs_constant_inst->opcode() != SpvOpConstant) {
166 return false;
167 }
168
169 // The left- and right-hand side instructions must have the same type.
170 if (lhs_constant_inst->type_id() != rhs_constant_inst->type_id()) {
171 return false;
172 }
173
174 // The expression 'LHS opcode RHS' must evaluate to the boolean constant.
175 auto lhs_constant =
176 context->get_constant_mgr()->FindDeclaredConstant(message_.lhs_id());
177 auto rhs_constant =
178 context->get_constant_mgr()->FindDeclaredConstant(message_.rhs_id());
179 bool expected_result = (boolean_constant->opcode() == SpvOpConstantTrue);
180
181 const auto binary_opcode = static_cast<SpvOp>(message_.opcode());
182
183 // We consider the floating point, signed and unsigned integer cases
184 // separately. In each case the logic is very similar.
185 if (lhs_constant->AsFloatConstant()) {
186 assert(rhs_constant->AsFloatConstant() &&
187 "Both constants should be of the same type.");
188 if (lhs_constant->type()->AsFloat()->width() == 32) {
189 if (!float_binop_evaluates_to(lhs_constant->GetFloat(),
190 rhs_constant->GetFloat(), binary_opcode,
191 expected_result)) {
192 return false;
193 }
194 } else {
195 assert(lhs_constant->type()->AsFloat()->width() == 64);
196 if (!float_binop_evaluates_to(lhs_constant->GetDouble(),
197 rhs_constant->GetDouble(), binary_opcode,
198 expected_result)) {
199 return false;
200 }
201 }
202 } else {
203 assert(lhs_constant->AsIntConstant() && "Constants should be in or float.");
204 assert(rhs_constant->AsIntConstant() &&
205 "Both constants should be of the same type.");
206 if (lhs_constant->type()->AsInteger()->IsSigned()) {
207 if (lhs_constant->type()->AsInteger()->width() == 32) {
208 if (!signed_int_binop_evaluates_to(lhs_constant->GetS32(),
209 rhs_constant->GetS32(),
210 binary_opcode, expected_result)) {
211 return false;
212 }
213 } else {
214 assert(lhs_constant->type()->AsInteger()->width() == 64);
215 if (!signed_int_binop_evaluates_to(lhs_constant->GetS64(),
216 rhs_constant->GetS64(),
217 binary_opcode, expected_result)) {
218 return false;
219 }
220 }
221 } else {
222 if (lhs_constant->type()->AsInteger()->width() == 32) {
223 if (!unsigned_int_binop_evaluates_to(lhs_constant->GetU32(),
224 rhs_constant->GetU32(),
225 binary_opcode, expected_result)) {
226 return false;
227 }
228 } else {
229 assert(lhs_constant->type()->AsInteger()->width() == 64);
230 if (!unsigned_int_binop_evaluates_to(lhs_constant->GetU64(),
231 rhs_constant->GetU64(),
232 binary_opcode, expected_result)) {
233 return false;
234 }
235 }
236 }
237 }
238
239 // The id use descriptor must identify some instruction
240 auto instruction =
241 FindInstructionContainingUse(message_.id_use_descriptor(), context);
242 if (instruction == nullptr) {
243 return false;
244 }
245
246 // The instruction must not be an OpPhi, as we cannot insert a binary
247 // operator instruction before an OpPhi.
248 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/2902): there is
249 // scope for being less conservative.
250 return instruction->opcode() != SpvOpPhi;
251 }
252
Apply(opt::IRContext * context,FactManager * fact_manager) const253 void TransformationReplaceBooleanConstantWithConstantBinary::Apply(
254 opt::IRContext* context, FactManager* fact_manager) const {
255 ApplyWithResult(context, fact_manager);
256 }
257
258 opt::Instruction*
ApplyWithResult(opt::IRContext * context,FactManager *) const259 TransformationReplaceBooleanConstantWithConstantBinary::ApplyWithResult(
260 opt::IRContext* context, FactManager* /*unused*/) const {
261 opt::analysis::Bool bool_type;
262 opt::Instruction::OperandList operands = {
263 {SPV_OPERAND_TYPE_ID, {message_.lhs_id()}},
264 {SPV_OPERAND_TYPE_ID, {message_.rhs_id()}}};
265 auto binary_instruction = MakeUnique<opt::Instruction>(
266 context, static_cast<SpvOp>(message_.opcode()),
267 context->get_type_mgr()->GetId(&bool_type),
268 message_.fresh_id_for_binary_operation(), operands);
269 opt::Instruction* result = binary_instruction.get();
270 auto instruction_containing_constant_use =
271 FindInstructionContainingUse(message_.id_use_descriptor(), context);
272
273 // We want to insert the new instruction before the instruction that contains
274 // the use of the boolean, but we need to go backwards one more instruction if
275 // the using instruction is preceded by a merge instruction.
276 auto instruction_before_which_to_insert = instruction_containing_constant_use;
277 {
278 opt::Instruction* previous_node =
279 instruction_before_which_to_insert->PreviousNode();
280 if (previous_node && (previous_node->opcode() == SpvOpLoopMerge ||
281 previous_node->opcode() == SpvOpSelectionMerge)) {
282 instruction_before_which_to_insert = previous_node;
283 }
284 }
285 instruction_before_which_to_insert->InsertBefore(
286 std::move(binary_instruction));
287 instruction_containing_constant_use->SetInOperand(
288 message_.id_use_descriptor().in_operand_index(),
289 {message_.fresh_id_for_binary_operation()});
290 fuzzerutil::UpdateModuleIdBound(context,
291 message_.fresh_id_for_binary_operation());
292 context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
293 return result;
294 }
295
296 protobufs::Transformation
ToMessage() const297 TransformationReplaceBooleanConstantWithConstantBinary::ToMessage() const {
298 protobufs::Transformation result;
299 *result.mutable_replace_boolean_constant_with_constant_binary() = message_;
300 return result;
301 }
302
303 } // namespace fuzz
304 } // namespace spvtools
305