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_move_instruction_down.h"
16
17 #include "source/fuzz/fuzzer_util.h"
18 #include "source/fuzz/instruction_descriptor.h"
19 #include "spirv/unified1/GLSL.std.450.h"
20
21 namespace spvtools {
22 namespace fuzz {
23 namespace {
24
25 const char* const kExtensionSetName = "GLSL.std.450";
26
GetExtensionSet(opt::IRContext * ir_context,const opt::Instruction & op_ext_inst)27 std::string GetExtensionSet(opt::IRContext* ir_context,
28 const opt::Instruction& op_ext_inst) {
29 assert(op_ext_inst.opcode() == SpvOpExtInst && "Wrong opcode");
30
31 const auto* ext_inst_import = ir_context->get_def_use_mgr()->GetDef(
32 op_ext_inst.GetSingleWordInOperand(0));
33 assert(ext_inst_import && "Extension set is not imported");
34
35 return ext_inst_import->GetInOperand(0).AsString();
36 }
37
38 } // namespace
39
TransformationMoveInstructionDown(protobufs::TransformationMoveInstructionDown message)40 TransformationMoveInstructionDown::TransformationMoveInstructionDown(
41 protobufs::TransformationMoveInstructionDown message)
42 : message_(std::move(message)) {}
43
TransformationMoveInstructionDown(const protobufs::InstructionDescriptor & instruction)44 TransformationMoveInstructionDown::TransformationMoveInstructionDown(
45 const protobufs::InstructionDescriptor& instruction) {
46 *message_.mutable_instruction() = instruction;
47 }
48
IsApplicable(opt::IRContext * ir_context,const TransformationContext & transformation_context) const49 bool TransformationMoveInstructionDown::IsApplicable(
50 opt::IRContext* ir_context,
51 const TransformationContext& transformation_context) const {
52 // |instruction| must be valid.
53 auto* inst = FindInstruction(message_.instruction(), ir_context);
54 if (!inst) {
55 return false;
56 }
57
58 // Instruction's opcode must be supported by this transformation.
59 if (!IsInstructionSupported(ir_context, *inst)) {
60 return false;
61 }
62
63 auto* inst_block = ir_context->get_instr_block(inst);
64 assert(inst_block &&
65 "Global instructions and function parameters are not supported");
66
67 auto inst_it = fuzzerutil::GetIteratorForInstruction(inst_block, inst);
68 assert(inst_it != inst_block->end() &&
69 "Can't get an iterator for the instruction");
70
71 // |instruction| can't be the last instruction in the block.
72 auto successor_it = ++inst_it;
73 if (successor_it == inst_block->end()) {
74 return false;
75 }
76
77 // We don't risk swapping a memory instruction with an unsupported one.
78 if (!IsSimpleInstruction(ir_context, *inst) &&
79 !IsInstructionSupported(ir_context, *successor_it)) {
80 return false;
81 }
82
83 // It must be safe to swap the instructions without changing the semantics of
84 // the module.
85 if (IsInstructionSupported(ir_context, *successor_it) &&
86 !CanSafelySwapInstructions(ir_context, *inst, *successor_it,
87 *transformation_context.GetFactManager())) {
88 return false;
89 }
90
91 // Check that we can insert |instruction| after |inst_it|.
92 auto successors_successor_it = ++inst_it;
93 if (successors_successor_it == inst_block->end() ||
94 !fuzzerutil::CanInsertOpcodeBeforeInstruction(inst->opcode(),
95 successors_successor_it)) {
96 return false;
97 }
98
99 // Check that |instruction|'s successor doesn't depend on the |instruction|.
100 if (inst->result_id()) {
101 for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) {
102 const auto& operand = successor_it->GetInOperand(i);
103 if (spvIsInIdType(operand.type) &&
104 operand.words[0] == inst->result_id()) {
105 return false;
106 }
107 }
108 }
109
110 return true;
111 }
112
Apply(opt::IRContext * ir_context,TransformationContext *) const113 void TransformationMoveInstructionDown::Apply(
114 opt::IRContext* ir_context, TransformationContext* /*unused*/) const {
115 auto* inst = FindInstruction(message_.instruction(), ir_context);
116 assert(inst &&
117 "The instruction should've been validated in the IsApplicable");
118
119 auto inst_it = fuzzerutil::GetIteratorForInstruction(
120 ir_context->get_instr_block(inst), inst);
121
122 // Move the instruction down in the block.
123 inst->InsertAfter(&*++inst_it);
124
125 ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisNone);
126 }
127
ToMessage() const128 protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const {
129 protobufs::Transformation result;
130 *result.mutable_move_instruction_down() = message_;
131 return result;
132 }
133
IsInstructionSupported(opt::IRContext * ir_context,const opt::Instruction & inst)134 bool TransformationMoveInstructionDown::IsInstructionSupported(
135 opt::IRContext* ir_context, const opt::Instruction& inst) {
136 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
137 // Add support for more instructions here.
138 return IsSimpleInstruction(ir_context, inst) ||
139 IsMemoryInstruction(ir_context, inst) || IsBarrierInstruction(inst);
140 }
141
IsSimpleInstruction(opt::IRContext * ir_context,const opt::Instruction & inst)142 bool TransformationMoveInstructionDown::IsSimpleInstruction(
143 opt::IRContext* ir_context, const opt::Instruction& inst) {
144 switch (inst.opcode()) {
145 case SpvOpNop:
146 case SpvOpUndef:
147 case SpvOpAccessChain:
148 case SpvOpInBoundsAccessChain:
149 // OpAccessChain and OpInBoundsAccessChain are considered simple
150 // instructions since they result in a pointer to the object in memory,
151 // not the object itself.
152 case SpvOpVectorExtractDynamic:
153 case SpvOpVectorInsertDynamic:
154 case SpvOpVectorShuffle:
155 case SpvOpCompositeConstruct:
156 case SpvOpCompositeExtract:
157 case SpvOpCompositeInsert:
158 case SpvOpCopyObject:
159 case SpvOpTranspose:
160 case SpvOpConvertFToU:
161 case SpvOpConvertFToS:
162 case SpvOpConvertSToF:
163 case SpvOpConvertUToF:
164 case SpvOpUConvert:
165 case SpvOpSConvert:
166 case SpvOpFConvert:
167 case SpvOpQuantizeToF16:
168 case SpvOpSatConvertSToU:
169 case SpvOpSatConvertUToS:
170 case SpvOpBitcast:
171 case SpvOpSNegate:
172 case SpvOpFNegate:
173 case SpvOpIAdd:
174 case SpvOpFAdd:
175 case SpvOpISub:
176 case SpvOpFSub:
177 case SpvOpIMul:
178 case SpvOpFMul:
179 case SpvOpUDiv:
180 case SpvOpSDiv:
181 case SpvOpFDiv:
182 case SpvOpUMod:
183 case SpvOpSRem:
184 case SpvOpSMod:
185 case SpvOpFRem:
186 case SpvOpFMod:
187 case SpvOpVectorTimesScalar:
188 case SpvOpMatrixTimesScalar:
189 case SpvOpVectorTimesMatrix:
190 case SpvOpMatrixTimesVector:
191 case SpvOpMatrixTimesMatrix:
192 case SpvOpOuterProduct:
193 case SpvOpDot:
194 case SpvOpIAddCarry:
195 case SpvOpISubBorrow:
196 case SpvOpUMulExtended:
197 case SpvOpSMulExtended:
198 case SpvOpAny:
199 case SpvOpAll:
200 case SpvOpIsNan:
201 case SpvOpIsInf:
202 case SpvOpIsFinite:
203 case SpvOpIsNormal:
204 case SpvOpSignBitSet:
205 case SpvOpLessOrGreater:
206 case SpvOpOrdered:
207 case SpvOpUnordered:
208 case SpvOpLogicalEqual:
209 case SpvOpLogicalNotEqual:
210 case SpvOpLogicalOr:
211 case SpvOpLogicalAnd:
212 case SpvOpLogicalNot:
213 case SpvOpSelect:
214 case SpvOpIEqual:
215 case SpvOpINotEqual:
216 case SpvOpUGreaterThan:
217 case SpvOpSGreaterThan:
218 case SpvOpUGreaterThanEqual:
219 case SpvOpSGreaterThanEqual:
220 case SpvOpULessThan:
221 case SpvOpSLessThan:
222 case SpvOpULessThanEqual:
223 case SpvOpSLessThanEqual:
224 case SpvOpFOrdEqual:
225 case SpvOpFUnordEqual:
226 case SpvOpFOrdNotEqual:
227 case SpvOpFUnordNotEqual:
228 case SpvOpFOrdLessThan:
229 case SpvOpFUnordLessThan:
230 case SpvOpFOrdGreaterThan:
231 case SpvOpFUnordGreaterThan:
232 case SpvOpFOrdLessThanEqual:
233 case SpvOpFUnordLessThanEqual:
234 case SpvOpFOrdGreaterThanEqual:
235 case SpvOpFUnordGreaterThanEqual:
236 case SpvOpShiftRightLogical:
237 case SpvOpShiftRightArithmetic:
238 case SpvOpShiftLeftLogical:
239 case SpvOpBitwiseOr:
240 case SpvOpBitwiseXor:
241 case SpvOpBitwiseAnd:
242 case SpvOpNot:
243 case SpvOpBitFieldInsert:
244 case SpvOpBitFieldSExtract:
245 case SpvOpBitFieldUExtract:
246 case SpvOpBitReverse:
247 case SpvOpBitCount:
248 case SpvOpCopyLogical:
249 return true;
250 case SpvOpExtInst: {
251 const auto* ext_inst_import =
252 ir_context->get_def_use_mgr()->GetDef(inst.GetSingleWordInOperand(0));
253
254 if (ext_inst_import->GetInOperand(0).AsString() != kExtensionSetName) {
255 return false;
256 }
257
258 switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
259 case GLSLstd450Round:
260 case GLSLstd450RoundEven:
261 case GLSLstd450Trunc:
262 case GLSLstd450FAbs:
263 case GLSLstd450SAbs:
264 case GLSLstd450FSign:
265 case GLSLstd450SSign:
266 case GLSLstd450Floor:
267 case GLSLstd450Ceil:
268 case GLSLstd450Fract:
269 case GLSLstd450Radians:
270 case GLSLstd450Degrees:
271 case GLSLstd450Sin:
272 case GLSLstd450Cos:
273 case GLSLstd450Tan:
274 case GLSLstd450Asin:
275 case GLSLstd450Acos:
276 case GLSLstd450Atan:
277 case GLSLstd450Sinh:
278 case GLSLstd450Cosh:
279 case GLSLstd450Tanh:
280 case GLSLstd450Asinh:
281 case GLSLstd450Acosh:
282 case GLSLstd450Atanh:
283 case GLSLstd450Atan2:
284 case GLSLstd450Pow:
285 case GLSLstd450Exp:
286 case GLSLstd450Log:
287 case GLSLstd450Exp2:
288 case GLSLstd450Log2:
289 case GLSLstd450Sqrt:
290 case GLSLstd450InverseSqrt:
291 case GLSLstd450Determinant:
292 case GLSLstd450MatrixInverse:
293 case GLSLstd450ModfStruct:
294 case GLSLstd450FMin:
295 case GLSLstd450UMin:
296 case GLSLstd450SMin:
297 case GLSLstd450FMax:
298 case GLSLstd450UMax:
299 case GLSLstd450SMax:
300 case GLSLstd450FClamp:
301 case GLSLstd450UClamp:
302 case GLSLstd450SClamp:
303 case GLSLstd450FMix:
304 case GLSLstd450IMix:
305 case GLSLstd450Step:
306 case GLSLstd450SmoothStep:
307 case GLSLstd450Fma:
308 case GLSLstd450FrexpStruct:
309 case GLSLstd450Ldexp:
310 case GLSLstd450PackSnorm4x8:
311 case GLSLstd450PackUnorm4x8:
312 case GLSLstd450PackSnorm2x16:
313 case GLSLstd450PackUnorm2x16:
314 case GLSLstd450PackHalf2x16:
315 case GLSLstd450PackDouble2x32:
316 case GLSLstd450UnpackSnorm2x16:
317 case GLSLstd450UnpackUnorm2x16:
318 case GLSLstd450UnpackHalf2x16:
319 case GLSLstd450UnpackSnorm4x8:
320 case GLSLstd450UnpackUnorm4x8:
321 case GLSLstd450UnpackDouble2x32:
322 case GLSLstd450Length:
323 case GLSLstd450Distance:
324 case GLSLstd450Cross:
325 case GLSLstd450Normalize:
326 case GLSLstd450FaceForward:
327 case GLSLstd450Reflect:
328 case GLSLstd450Refract:
329 case GLSLstd450FindILsb:
330 case GLSLstd450FindSMsb:
331 case GLSLstd450FindUMsb:
332 case GLSLstd450NMin:
333 case GLSLstd450NMax:
334 case GLSLstd450NClamp:
335 return true;
336 default:
337 return false;
338 }
339 }
340 default:
341 return false;
342 }
343 }
344
IsMemoryReadInstruction(opt::IRContext * ir_context,const opt::Instruction & inst)345 bool TransformationMoveInstructionDown::IsMemoryReadInstruction(
346 opt::IRContext* ir_context, const opt::Instruction& inst) {
347 switch (inst.opcode()) {
348 // Some simple instructions.
349 case SpvOpLoad:
350 case SpvOpCopyMemory:
351 // Image instructions.
352 case SpvOpImageSampleImplicitLod:
353 case SpvOpImageSampleExplicitLod:
354 case SpvOpImageSampleDrefImplicitLod:
355 case SpvOpImageSampleDrefExplicitLod:
356 case SpvOpImageSampleProjImplicitLod:
357 case SpvOpImageSampleProjExplicitLod:
358 case SpvOpImageSampleProjDrefImplicitLod:
359 case SpvOpImageSampleProjDrefExplicitLod:
360 case SpvOpImageFetch:
361 case SpvOpImageGather:
362 case SpvOpImageDrefGather:
363 case SpvOpImageRead:
364 case SpvOpImageSparseSampleImplicitLod:
365 case SpvOpImageSparseSampleExplicitLod:
366 case SpvOpImageSparseSampleDrefImplicitLod:
367 case SpvOpImageSparseSampleDrefExplicitLod:
368 case SpvOpImageSparseSampleProjImplicitLod:
369 case SpvOpImageSparseSampleProjExplicitLod:
370 case SpvOpImageSparseSampleProjDrefImplicitLod:
371 case SpvOpImageSparseSampleProjDrefExplicitLod:
372 case SpvOpImageSparseFetch:
373 case SpvOpImageSparseGather:
374 case SpvOpImageSparseDrefGather:
375 case SpvOpImageSparseRead:
376 // Atomic instructions.
377 case SpvOpAtomicLoad:
378 case SpvOpAtomicExchange:
379 case SpvOpAtomicCompareExchange:
380 case SpvOpAtomicCompareExchangeWeak:
381 case SpvOpAtomicIIncrement:
382 case SpvOpAtomicIDecrement:
383 case SpvOpAtomicIAdd:
384 case SpvOpAtomicISub:
385 case SpvOpAtomicSMin:
386 case SpvOpAtomicUMin:
387 case SpvOpAtomicSMax:
388 case SpvOpAtomicUMax:
389 case SpvOpAtomicAnd:
390 case SpvOpAtomicOr:
391 case SpvOpAtomicXor:
392 case SpvOpAtomicFlagTestAndSet:
393 return true;
394 // Extensions.
395 case SpvOpExtInst: {
396 if (GetExtensionSet(ir_context, inst) != kExtensionSetName) {
397 return false;
398 }
399
400 switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
401 case GLSLstd450InterpolateAtCentroid:
402 case GLSLstd450InterpolateAtOffset:
403 case GLSLstd450InterpolateAtSample:
404 return true;
405 default:
406 return false;
407 }
408 }
409 default:
410 return false;
411 }
412 }
413
GetMemoryReadTarget(opt::IRContext * ir_context,const opt::Instruction & inst)414 uint32_t TransformationMoveInstructionDown::GetMemoryReadTarget(
415 opt::IRContext* ir_context, const opt::Instruction& inst) {
416 (void)ir_context; // |ir_context| is only used in assertions.
417 assert(IsMemoryReadInstruction(ir_context, inst) &&
418 "|inst| is not a memory read instruction");
419
420 switch (inst.opcode()) {
421 // Simple instructions.
422 case SpvOpLoad:
423 // Image instructions.
424 case SpvOpImageSampleImplicitLod:
425 case SpvOpImageSampleExplicitLod:
426 case SpvOpImageSampleDrefImplicitLod:
427 case SpvOpImageSampleDrefExplicitLod:
428 case SpvOpImageSampleProjImplicitLod:
429 case SpvOpImageSampleProjExplicitLod:
430 case SpvOpImageSampleProjDrefImplicitLod:
431 case SpvOpImageSampleProjDrefExplicitLod:
432 case SpvOpImageFetch:
433 case SpvOpImageGather:
434 case SpvOpImageDrefGather:
435 case SpvOpImageRead:
436 case SpvOpImageSparseSampleImplicitLod:
437 case SpvOpImageSparseSampleExplicitLod:
438 case SpvOpImageSparseSampleDrefImplicitLod:
439 case SpvOpImageSparseSampleDrefExplicitLod:
440 case SpvOpImageSparseSampleProjImplicitLod:
441 case SpvOpImageSparseSampleProjExplicitLod:
442 case SpvOpImageSparseSampleProjDrefImplicitLod:
443 case SpvOpImageSparseSampleProjDrefExplicitLod:
444 case SpvOpImageSparseFetch:
445 case SpvOpImageSparseGather:
446 case SpvOpImageSparseDrefGather:
447 case SpvOpImageSparseRead:
448 // Atomic instructions.
449 case SpvOpAtomicLoad:
450 case SpvOpAtomicExchange:
451 case SpvOpAtomicCompareExchange:
452 case SpvOpAtomicCompareExchangeWeak:
453 case SpvOpAtomicIIncrement:
454 case SpvOpAtomicIDecrement:
455 case SpvOpAtomicIAdd:
456 case SpvOpAtomicISub:
457 case SpvOpAtomicSMin:
458 case SpvOpAtomicUMin:
459 case SpvOpAtomicSMax:
460 case SpvOpAtomicUMax:
461 case SpvOpAtomicAnd:
462 case SpvOpAtomicOr:
463 case SpvOpAtomicXor:
464 case SpvOpAtomicFlagTestAndSet:
465 return inst.GetSingleWordInOperand(0);
466 case SpvOpCopyMemory:
467 return inst.GetSingleWordInOperand(1);
468 case SpvOpExtInst: {
469 assert(GetExtensionSet(ir_context, inst) == kExtensionSetName &&
470 "Extension set is not supported");
471
472 switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
473 case GLSLstd450InterpolateAtCentroid:
474 case GLSLstd450InterpolateAtOffset:
475 case GLSLstd450InterpolateAtSample:
476 return inst.GetSingleWordInOperand(2);
477 default:
478 // This assertion will fail if not all memory read extension
479 // instructions are handled in the switch.
480 assert(false && "Not all memory opcodes are handled");
481 return 0;
482 }
483 }
484 default:
485 // This assertion will fail if not all memory read opcodes are handled in
486 // the switch.
487 assert(false && "Not all memory opcodes are handled");
488 return 0;
489 }
490 }
491
IsMemoryWriteInstruction(opt::IRContext * ir_context,const opt::Instruction & inst)492 bool TransformationMoveInstructionDown::IsMemoryWriteInstruction(
493 opt::IRContext* ir_context, const opt::Instruction& inst) {
494 switch (inst.opcode()) {
495 // Simple Instructions.
496 case SpvOpStore:
497 case SpvOpCopyMemory:
498 // Image instructions.
499 case SpvOpImageWrite:
500 // Atomic instructions.
501 case SpvOpAtomicStore:
502 case SpvOpAtomicExchange:
503 case SpvOpAtomicCompareExchange:
504 case SpvOpAtomicCompareExchangeWeak:
505 case SpvOpAtomicIIncrement:
506 case SpvOpAtomicIDecrement:
507 case SpvOpAtomicIAdd:
508 case SpvOpAtomicISub:
509 case SpvOpAtomicSMin:
510 case SpvOpAtomicUMin:
511 case SpvOpAtomicSMax:
512 case SpvOpAtomicUMax:
513 case SpvOpAtomicAnd:
514 case SpvOpAtomicOr:
515 case SpvOpAtomicXor:
516 case SpvOpAtomicFlagTestAndSet:
517 case SpvOpAtomicFlagClear:
518 return true;
519 // Extensions.
520 case SpvOpExtInst: {
521 if (GetExtensionSet(ir_context, inst) != kExtensionSetName) {
522 return false;
523 }
524
525 auto extension = static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1));
526 return extension == GLSLstd450Modf || extension == GLSLstd450Frexp;
527 }
528 default:
529 return false;
530 }
531 }
532
GetMemoryWriteTarget(opt::IRContext * ir_context,const opt::Instruction & inst)533 uint32_t TransformationMoveInstructionDown::GetMemoryWriteTarget(
534 opt::IRContext* ir_context, const opt::Instruction& inst) {
535 (void)ir_context; // |ir_context| is only used in assertions.
536 assert(IsMemoryWriteInstruction(ir_context, inst) &&
537 "|inst| is not a memory write instruction");
538
539 switch (inst.opcode()) {
540 case SpvOpStore:
541 case SpvOpCopyMemory:
542 case SpvOpImageWrite:
543 case SpvOpAtomicStore:
544 case SpvOpAtomicExchange:
545 case SpvOpAtomicCompareExchange:
546 case SpvOpAtomicCompareExchangeWeak:
547 case SpvOpAtomicIIncrement:
548 case SpvOpAtomicIDecrement:
549 case SpvOpAtomicIAdd:
550 case SpvOpAtomicISub:
551 case SpvOpAtomicSMin:
552 case SpvOpAtomicUMin:
553 case SpvOpAtomicSMax:
554 case SpvOpAtomicUMax:
555 case SpvOpAtomicAnd:
556 case SpvOpAtomicOr:
557 case SpvOpAtomicXor:
558 case SpvOpAtomicFlagTestAndSet:
559 case SpvOpAtomicFlagClear:
560 return inst.GetSingleWordInOperand(0);
561 case SpvOpExtInst: {
562 assert(GetExtensionSet(ir_context, inst) == kExtensionSetName &&
563 "Extension set is not supported");
564
565 switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
566 case GLSLstd450Modf:
567 case GLSLstd450Frexp:
568 return inst.GetSingleWordInOperand(3);
569 default:
570 // This assertion will fail if not all memory write extension
571 // instructions are handled in the switch.
572 assert(false && "Not all opcodes are handled");
573 return 0;
574 }
575 }
576 default:
577 // This assertion will fail if not all memory write opcodes are handled in
578 // the switch.
579 assert(false && "Not all opcodes are handled");
580 return 0;
581 }
582 }
583
IsMemoryInstruction(opt::IRContext * ir_context,const opt::Instruction & inst)584 bool TransformationMoveInstructionDown::IsMemoryInstruction(
585 opt::IRContext* ir_context, const opt::Instruction& inst) {
586 return IsMemoryReadInstruction(ir_context, inst) ||
587 IsMemoryWriteInstruction(ir_context, inst);
588 }
589
IsBarrierInstruction(const opt::Instruction & inst)590 bool TransformationMoveInstructionDown::IsBarrierInstruction(
591 const opt::Instruction& inst) {
592 switch (inst.opcode()) {
593 case SpvOpMemoryBarrier:
594 case SpvOpControlBarrier:
595 case SpvOpMemoryNamedBarrier:
596 return true;
597 default:
598 return false;
599 }
600 }
601
CanSafelySwapInstructions(opt::IRContext * ir_context,const opt::Instruction & a,const opt::Instruction & b,const FactManager & fact_manager)602 bool TransformationMoveInstructionDown::CanSafelySwapInstructions(
603 opt::IRContext* ir_context, const opt::Instruction& a,
604 const opt::Instruction& b, const FactManager& fact_manager) {
605 assert(IsInstructionSupported(ir_context, a) &&
606 IsInstructionSupported(ir_context, b) &&
607 "Both opcodes must be supported");
608
609 // One of opcodes is simple - we can swap them without any side-effects.
610 if (IsSimpleInstruction(ir_context, a) ||
611 IsSimpleInstruction(ir_context, b)) {
612 return true;
613 }
614
615 // Both parameters are either memory instruction or barriers.
616
617 // One of the opcodes is a barrier - can't swap them.
618 if (IsBarrierInstruction(a) || IsBarrierInstruction(b)) {
619 return false;
620 }
621
622 // Both parameters are memory instructions.
623
624 // Both parameters only read from memory - it's OK to swap them.
625 if (!IsMemoryWriteInstruction(ir_context, a) &&
626 !IsMemoryWriteInstruction(ir_context, b)) {
627 return true;
628 }
629
630 // At least one of parameters is a memory read instruction.
631
632 // In theory, we can swap two memory instructions, one of which reads
633 // from the memory, if the read target (the pointer the memory is read from)
634 // and the write target (the memory is written into):
635 // - point to different memory regions
636 // - point to the same region with irrelevant value
637 // - point to the same region and the region is not used anymore.
638 //
639 // However, we can't currently determine if two pointers point to two
640 // different memory regions. That being said, if two pointers are not
641 // synonymous, they still might point to the same memory region. For example:
642 // %1 = OpVariable ...
643 // %2 = OpAccessChain %1 0
644 // %3 = OpAccessChain %1 0
645 // In this pseudo-code, %2 and %3 are not synonymous but point to the same
646 // memory location. This implies that we can't determine if some memory
647 // location is not used in the block.
648 //
649 // With this in mind, consider two cases (we will build a table for each one):
650 // - one instruction only reads from memory, the other one only writes to it.
651 // S - both point to the same memory region.
652 // D - both point to different memory regions.
653 // 0, 1, 2 - neither, one of or both of the memory regions are irrelevant.
654 // |-| - can't swap; |+| - can swap.
655 // | 0 | 1 | 2 |
656 // S : - + +
657 // D : + + +
658 // - both instructions write to memory. Notation is the same.
659 // | 0 | 1 | 2 |
660 // S : * + +
661 // D : + + +
662 // * - we can swap two instructions that write into the same non-irrelevant
663 // memory region if the written value is the same.
664 //
665 // Note that we can't always distinguish between S and D. Also note that
666 // in case of S, if one of the instructions is marked with
667 // PointeeValueIsIrrelevant, then the pointee of the other one is irrelevant
668 // as well even if the instruction is not marked with that fact.
669 //
670 // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3723):
671 // This procedure can be improved when we can determine if two pointers point
672 // to different memory regions.
673
674 // From now on we will denote an instruction that:
675 // - only reads from memory - R
676 // - only writes into memory - W
677 // - reads and writes - RW
678 //
679 // Both |a| and |b| can be either W or RW at this point. Additionally, at most
680 // one of them can be R. The procedure below checks all possible combinations
681 // of R, W and RW according to the tables above. We conservatively assume that
682 // both |a| and |b| point to the same memory region.
683
684 auto memory_is_irrelevant = [ir_context, &fact_manager](uint32_t id) {
685 const auto* inst = ir_context->get_def_use_mgr()->GetDef(id);
686 if (!inst->type_id()) {
687 return false;
688 }
689
690 const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id());
691 assert(type && "|id| has invalid type");
692
693 if (!type->AsPointer()) {
694 return false;
695 }
696
697 return fact_manager.PointeeValueIsIrrelevant(id);
698 };
699
700 if (IsMemoryWriteInstruction(ir_context, a) &&
701 IsMemoryWriteInstruction(ir_context, b) &&
702 (memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) ||
703 memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b)))) {
704 // We ignore the case when the written value is the same. This is because
705 // the written value might not be equal to any of the instruction's
706 // operands.
707 return true;
708 }
709
710 if (IsMemoryReadInstruction(ir_context, a) &&
711 IsMemoryWriteInstruction(ir_context, b) &&
712 !memory_is_irrelevant(GetMemoryReadTarget(ir_context, a)) &&
713 !memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b))) {
714 return false;
715 }
716
717 if (IsMemoryWriteInstruction(ir_context, a) &&
718 IsMemoryReadInstruction(ir_context, b) &&
719 !memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) &&
720 !memory_is_irrelevant(GetMemoryReadTarget(ir_context, b))) {
721 return false;
722 }
723
724 return IsMemoryReadInstruction(ir_context, a) ||
725 IsMemoryReadInstruction(ir_context, b);
726 }
727
GetFreshIds() const728 std::unordered_set<uint32_t> TransformationMoveInstructionDown::GetFreshIds()
729 const {
730 return std::unordered_set<uint32_t>();
731 }
732
733 } // namespace fuzz
734 } // namespace spvtools
735