| // Copyright (c) 2020 Vasyl Teliman | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "source/fuzz/transformation_move_instruction_down.h" | |
| #include "source/fuzz/fuzzer_util.h" | |
| #include "source/fuzz/instruction_descriptor.h" | |
| #include "spirv/unified1/GLSL.std.450.h" | |
| namespace spvtools { | |
| namespace fuzz { | |
| namespace { | |
| const char* const kExtensionSetName = "GLSL.std.450"; | |
| std::string GetExtensionSet(opt::IRContext* ir_context, | |
| const opt::Instruction& op_ext_inst) { | |
| assert(op_ext_inst.opcode() == spv::Op::OpExtInst && "Wrong opcode"); | |
| const auto* ext_inst_import = ir_context->get_def_use_mgr()->GetDef( | |
| op_ext_inst.GetSingleWordInOperand(0)); | |
| assert(ext_inst_import && "Extension set is not imported"); | |
| return ext_inst_import->GetInOperand(0).AsString(); | |
| } | |
| } // namespace | |
| TransformationMoveInstructionDown::TransformationMoveInstructionDown( | |
| protobufs::TransformationMoveInstructionDown message) | |
| : message_(std::move(message)) {} | |
| TransformationMoveInstructionDown::TransformationMoveInstructionDown( | |
| const protobufs::InstructionDescriptor& instruction) { | |
| *message_.mutable_instruction() = instruction; | |
| } | |
| bool TransformationMoveInstructionDown::IsApplicable( | |
| opt::IRContext* ir_context, | |
| const TransformationContext& transformation_context) const { | |
| // |instruction| must be valid. | |
| auto* inst = FindInstruction(message_.instruction(), ir_context); | |
| if (!inst) { | |
| return false; | |
| } | |
| // Instruction's opcode must be supported by this transformation. | |
| if (!IsInstructionSupported(ir_context, *inst)) { | |
| return false; | |
| } | |
| auto* inst_block = ir_context->get_instr_block(inst); | |
| assert(inst_block && | |
| "Global instructions and function parameters are not supported"); | |
| auto inst_it = fuzzerutil::GetIteratorForInstruction(inst_block, inst); | |
| assert(inst_it != inst_block->end() && | |
| "Can't get an iterator for the instruction"); | |
| // |instruction| can't be the last instruction in the block. | |
| auto successor_it = ++inst_it; | |
| if (successor_it == inst_block->end()) { | |
| return false; | |
| } | |
| // We don't risk swapping a memory instruction with an unsupported one. | |
| if (!IsSimpleInstruction(ir_context, *inst) && | |
| !IsInstructionSupported(ir_context, *successor_it)) { | |
| return false; | |
| } | |
| // It must be safe to swap the instructions without changing the semantics of | |
| // the module. | |
| if (IsInstructionSupported(ir_context, *successor_it) && | |
| !CanSafelySwapInstructions(ir_context, *inst, *successor_it, | |
| *transformation_context.GetFactManager())) { | |
| return false; | |
| } | |
| // Check that we can insert |instruction| after |inst_it|. | |
| auto successors_successor_it = ++inst_it; | |
| if (successors_successor_it == inst_block->end() || | |
| !fuzzerutil::CanInsertOpcodeBeforeInstruction(inst->opcode(), | |
| successors_successor_it)) { | |
| return false; | |
| } | |
| // Check that |instruction|'s successor doesn't depend on the |instruction|. | |
| if (inst->result_id()) { | |
| for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) { | |
| const auto& operand = successor_it->GetInOperand(i); | |
| if (spvIsInIdType(operand.type) && | |
| operand.words[0] == inst->result_id()) { | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| void TransformationMoveInstructionDown::Apply( | |
| opt::IRContext* ir_context, TransformationContext* /*unused*/) const { | |
| auto* inst = FindInstruction(message_.instruction(), ir_context); | |
| assert(inst && | |
| "The instruction should've been validated in the IsApplicable"); | |
| auto inst_it = fuzzerutil::GetIteratorForInstruction( | |
| ir_context->get_instr_block(inst), inst); | |
| // Move the instruction down in the block. | |
| inst->InsertAfter(&*++inst_it); | |
| ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisNone); | |
| } | |
| protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const { | |
| protobufs::Transformation result; | |
| *result.mutable_move_instruction_down() = message_; | |
| return result; | |
| } | |
| bool TransformationMoveInstructionDown::IsInstructionSupported( | |
| opt::IRContext* ir_context, const opt::Instruction& inst) { | |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605): | |
| // Add support for more instructions here. | |
| return IsSimpleInstruction(ir_context, inst) || | |
| IsMemoryInstruction(ir_context, inst) || IsBarrierInstruction(inst); | |
| } | |
| bool TransformationMoveInstructionDown::IsSimpleInstruction( | |
| opt::IRContext* ir_context, const opt::Instruction& inst) { | |
| switch (inst.opcode()) { | |
| case spv::Op::OpNop: | |
| case spv::Op::OpUndef: | |
| case spv::Op::OpAccessChain: | |
| case spv::Op::OpInBoundsAccessChain: | |
| // OpAccessChain and OpInBoundsAccessChain are considered simple | |
| // instructions since they result in a pointer to the object in memory, | |
| // not the object itself. | |
| case spv::Op::OpVectorExtractDynamic: | |
| case spv::Op::OpVectorInsertDynamic: | |
| case spv::Op::OpVectorShuffle: | |
| case spv::Op::OpCompositeConstruct: | |
| case spv::Op::OpCompositeExtract: | |
| case spv::Op::OpCompositeInsert: | |
| case spv::Op::OpCopyObject: | |
| case spv::Op::OpTranspose: | |
| case spv::Op::OpConvertFToU: | |
| case spv::Op::OpConvertFToS: | |
| case spv::Op::OpConvertSToF: | |
| case spv::Op::OpConvertUToF: | |
| case spv::Op::OpUConvert: | |
| case spv::Op::OpSConvert: | |
| case spv::Op::OpFConvert: | |
| case spv::Op::OpQuantizeToF16: | |
| case spv::Op::OpSatConvertSToU: | |
| case spv::Op::OpSatConvertUToS: | |
| case spv::Op::OpBitcast: | |
| case spv::Op::OpSNegate: | |
| case spv::Op::OpFNegate: | |
| case spv::Op::OpIAdd: | |
| case spv::Op::OpFAdd: | |
| case spv::Op::OpISub: | |
| case spv::Op::OpFSub: | |
| case spv::Op::OpIMul: | |
| case spv::Op::OpFMul: | |
| case spv::Op::OpUDiv: | |
| case spv::Op::OpSDiv: | |
| case spv::Op::OpFDiv: | |
| case spv::Op::OpUMod: | |
| case spv::Op::OpSRem: | |
| case spv::Op::OpSMod: | |
| case spv::Op::OpFRem: | |
| case spv::Op::OpFMod: | |
| case spv::Op::OpVectorTimesScalar: | |
| case spv::Op::OpMatrixTimesScalar: | |
| case spv::Op::OpVectorTimesMatrix: | |
| case spv::Op::OpMatrixTimesVector: | |
| case spv::Op::OpMatrixTimesMatrix: | |
| case spv::Op::OpOuterProduct: | |
| case spv::Op::OpDot: | |
| case spv::Op::OpIAddCarry: | |
| case spv::Op::OpISubBorrow: | |
| case spv::Op::OpUMulExtended: | |
| case spv::Op::OpSMulExtended: | |
| case spv::Op::OpAny: | |
| case spv::Op::OpAll: | |
| case spv::Op::OpIsNan: | |
| case spv::Op::OpIsInf: | |
| case spv::Op::OpIsFinite: | |
| case spv::Op::OpIsNormal: | |
| case spv::Op::OpSignBitSet: | |
| case spv::Op::OpLessOrGreater: | |
| case spv::Op::OpOrdered: | |
| case spv::Op::OpUnordered: | |
| case spv::Op::OpLogicalEqual: | |
| case spv::Op::OpLogicalNotEqual: | |
| case spv::Op::OpLogicalOr: | |
| case spv::Op::OpLogicalAnd: | |
| case spv::Op::OpLogicalNot: | |
| case spv::Op::OpSelect: | |
| case spv::Op::OpIEqual: | |
| case spv::Op::OpINotEqual: | |
| case spv::Op::OpUGreaterThan: | |
| case spv::Op::OpSGreaterThan: | |
| case spv::Op::OpUGreaterThanEqual: | |
| case spv::Op::OpSGreaterThanEqual: | |
| case spv::Op::OpULessThan: | |
| case spv::Op::OpSLessThan: | |
| case spv::Op::OpULessThanEqual: | |
| case spv::Op::OpSLessThanEqual: | |
| case spv::Op::OpFOrdEqual: | |
| case spv::Op::OpFUnordEqual: | |
| case spv::Op::OpFOrdNotEqual: | |
| case spv::Op::OpFUnordNotEqual: | |
| case spv::Op::OpFOrdLessThan: | |
| case spv::Op::OpFUnordLessThan: | |
| case spv::Op::OpFOrdGreaterThan: | |
| case spv::Op::OpFUnordGreaterThan: | |
| case spv::Op::OpFOrdLessThanEqual: | |
| case spv::Op::OpFUnordLessThanEqual: | |
| case spv::Op::OpFOrdGreaterThanEqual: | |
| case spv::Op::OpFUnordGreaterThanEqual: | |
| case spv::Op::OpShiftRightLogical: | |
| case spv::Op::OpShiftRightArithmetic: | |
| case spv::Op::OpShiftLeftLogical: | |
| case spv::Op::OpBitwiseOr: | |
| case spv::Op::OpBitwiseXor: | |
| case spv::Op::OpBitwiseAnd: | |
| case spv::Op::OpNot: | |
| case spv::Op::OpBitFieldInsert: | |
| case spv::Op::OpBitFieldSExtract: | |
| case spv::Op::OpBitFieldUExtract: | |
| case spv::Op::OpBitReverse: | |
| case spv::Op::OpBitCount: | |
| case spv::Op::OpCopyLogical: | |
| return true; | |
| case spv::Op::OpExtInst: { | |
| const auto* ext_inst_import = | |
| ir_context->get_def_use_mgr()->GetDef(inst.GetSingleWordInOperand(0)); | |
| if (ext_inst_import->GetInOperand(0).AsString() != kExtensionSetName) { | |
| return false; | |
| } | |
| switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) { | |
| case GLSLstd450Round: | |
| case GLSLstd450RoundEven: | |
| case GLSLstd450Trunc: | |
| case GLSLstd450FAbs: | |
| case GLSLstd450SAbs: | |
| case GLSLstd450FSign: | |
| case GLSLstd450SSign: | |
| case GLSLstd450Floor: | |
| case GLSLstd450Ceil: | |
| case GLSLstd450Fract: | |
| case GLSLstd450Radians: | |
| case GLSLstd450Degrees: | |
| case GLSLstd450Sin: | |
| case GLSLstd450Cos: | |
| case GLSLstd450Tan: | |
| case GLSLstd450Asin: | |
| case GLSLstd450Acos: | |
| case GLSLstd450Atan: | |
| case GLSLstd450Sinh: | |
| case GLSLstd450Cosh: | |
| case GLSLstd450Tanh: | |
| case GLSLstd450Asinh: | |
| case GLSLstd450Acosh: | |
| case GLSLstd450Atanh: | |
| case GLSLstd450Atan2: | |
| case GLSLstd450Pow: | |
| case GLSLstd450Exp: | |
| case GLSLstd450Log: | |
| case GLSLstd450Exp2: | |
| case GLSLstd450Log2: | |
| case GLSLstd450Sqrt: | |
| case GLSLstd450InverseSqrt: | |
| case GLSLstd450Determinant: | |
| case GLSLstd450MatrixInverse: | |
| case GLSLstd450ModfStruct: | |
| case GLSLstd450FMin: | |
| case GLSLstd450UMin: | |
| case GLSLstd450SMin: | |
| case GLSLstd450FMax: | |
| case GLSLstd450UMax: | |
| case GLSLstd450SMax: | |
| case GLSLstd450FClamp: | |
| case GLSLstd450UClamp: | |
| case GLSLstd450SClamp: | |
| case GLSLstd450FMix: | |
| case GLSLstd450IMix: | |
| case GLSLstd450Step: | |
| case GLSLstd450SmoothStep: | |
| case GLSLstd450Fma: | |
| case GLSLstd450FrexpStruct: | |
| case GLSLstd450Ldexp: | |
| case GLSLstd450PackSnorm4x8: | |
| case GLSLstd450PackUnorm4x8: | |
| case GLSLstd450PackSnorm2x16: | |
| case GLSLstd450PackUnorm2x16: | |
| case GLSLstd450PackHalf2x16: | |
| case GLSLstd450PackDouble2x32: | |
| case GLSLstd450UnpackSnorm2x16: | |
| case GLSLstd450UnpackUnorm2x16: | |
| case GLSLstd450UnpackHalf2x16: | |
| case GLSLstd450UnpackSnorm4x8: | |
| case GLSLstd450UnpackUnorm4x8: | |
| case GLSLstd450UnpackDouble2x32: | |
| case GLSLstd450Length: | |
| case GLSLstd450Distance: | |
| case GLSLstd450Cross: | |
| case GLSLstd450Normalize: | |
| case GLSLstd450FaceForward: | |
| case GLSLstd450Reflect: | |
| case GLSLstd450Refract: | |
| case GLSLstd450FindILsb: | |
| case GLSLstd450FindSMsb: | |
| case GLSLstd450FindUMsb: | |
| case GLSLstd450NMin: | |
| case GLSLstd450NMax: | |
| case GLSLstd450NClamp: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| default: | |
| return false; | |
| } | |
| } | |
| bool TransformationMoveInstructionDown::IsMemoryReadInstruction( | |
| opt::IRContext* ir_context, const opt::Instruction& inst) { | |
| switch (inst.opcode()) { | |
| // Some simple instructions. | |
| case spv::Op::OpLoad: | |
| case spv::Op::OpCopyMemory: | |
| // Image instructions. | |
| case spv::Op::OpImageSampleImplicitLod: | |
| case spv::Op::OpImageSampleExplicitLod: | |
| case spv::Op::OpImageSampleDrefImplicitLod: | |
| case spv::Op::OpImageSampleDrefExplicitLod: | |
| case spv::Op::OpImageSampleProjImplicitLod: | |
| case spv::Op::OpImageSampleProjExplicitLod: | |
| case spv::Op::OpImageSampleProjDrefImplicitLod: | |
| case spv::Op::OpImageSampleProjDrefExplicitLod: | |
| case spv::Op::OpImageFetch: | |
| case spv::Op::OpImageGather: | |
| case spv::Op::OpImageDrefGather: | |
| case spv::Op::OpImageRead: | |
| case spv::Op::OpImageSparseSampleImplicitLod: | |
| case spv::Op::OpImageSparseSampleExplicitLod: | |
| case spv::Op::OpImageSparseSampleDrefImplicitLod: | |
| case spv::Op::OpImageSparseSampleDrefExplicitLod: | |
| case spv::Op::OpImageSparseSampleProjImplicitLod: | |
| case spv::Op::OpImageSparseSampleProjExplicitLod: | |
| case spv::Op::OpImageSparseSampleProjDrefImplicitLod: | |
| case spv::Op::OpImageSparseSampleProjDrefExplicitLod: | |
| case spv::Op::OpImageSparseFetch: | |
| case spv::Op::OpImageSparseGather: | |
| case spv::Op::OpImageSparseDrefGather: | |
| case spv::Op::OpImageSparseRead: | |
| // Atomic instructions. | |
| case spv::Op::OpAtomicLoad: | |
| case spv::Op::OpAtomicExchange: | |
| case spv::Op::OpAtomicCompareExchange: | |
| case spv::Op::OpAtomicCompareExchangeWeak: | |
| case spv::Op::OpAtomicIIncrement: | |
| case spv::Op::OpAtomicIDecrement: | |
| case spv::Op::OpAtomicIAdd: | |
| case spv::Op::OpAtomicISub: | |
| case spv::Op::OpAtomicSMin: | |
| case spv::Op::OpAtomicUMin: | |
| case spv::Op::OpAtomicSMax: | |
| case spv::Op::OpAtomicUMax: | |
| case spv::Op::OpAtomicAnd: | |
| case spv::Op::OpAtomicOr: | |
| case spv::Op::OpAtomicXor: | |
| case spv::Op::OpAtomicFlagTestAndSet: | |
| return true; | |
| // Extensions. | |
| case spv::Op::OpExtInst: { | |
| if (GetExtensionSet(ir_context, inst) != kExtensionSetName) { | |
| return false; | |
| } | |
| switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) { | |
| case GLSLstd450InterpolateAtCentroid: | |
| case GLSLstd450InterpolateAtOffset: | |
| case GLSLstd450InterpolateAtSample: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| default: | |
| return false; | |
| } | |
| } | |
| uint32_t TransformationMoveInstructionDown::GetMemoryReadTarget( | |
| opt::IRContext* ir_context, const opt::Instruction& inst) { | |
| (void)ir_context; // |ir_context| is only used in assertions. | |
| assert(IsMemoryReadInstruction(ir_context, inst) && | |
| "|inst| is not a memory read instruction"); | |
| switch (inst.opcode()) { | |
| // Simple instructions. | |
| case spv::Op::OpLoad: | |
| // Image instructions. | |
| case spv::Op::OpImageSampleImplicitLod: | |
| case spv::Op::OpImageSampleExplicitLod: | |
| case spv::Op::OpImageSampleDrefImplicitLod: | |
| case spv::Op::OpImageSampleDrefExplicitLod: | |
| case spv::Op::OpImageSampleProjImplicitLod: | |
| case spv::Op::OpImageSampleProjExplicitLod: | |
| case spv::Op::OpImageSampleProjDrefImplicitLod: | |
| case spv::Op::OpImageSampleProjDrefExplicitLod: | |
| case spv::Op::OpImageFetch: | |
| case spv::Op::OpImageGather: | |
| case spv::Op::OpImageDrefGather: | |
| case spv::Op::OpImageRead: | |
| case spv::Op::OpImageSparseSampleImplicitLod: | |
| case spv::Op::OpImageSparseSampleExplicitLod: | |
| case spv::Op::OpImageSparseSampleDrefImplicitLod: | |
| case spv::Op::OpImageSparseSampleDrefExplicitLod: | |
| case spv::Op::OpImageSparseSampleProjImplicitLod: | |
| case spv::Op::OpImageSparseSampleProjExplicitLod: | |
| case spv::Op::OpImageSparseSampleProjDrefImplicitLod: | |
| case spv::Op::OpImageSparseSampleProjDrefExplicitLod: | |
| case spv::Op::OpImageSparseFetch: | |
| case spv::Op::OpImageSparseGather: | |
| case spv::Op::OpImageSparseDrefGather: | |
| case spv::Op::OpImageSparseRead: | |
| // Atomic instructions. | |
| case spv::Op::OpAtomicLoad: | |
| case spv::Op::OpAtomicExchange: | |
| case spv::Op::OpAtomicCompareExchange: | |
| case spv::Op::OpAtomicCompareExchangeWeak: | |
| case spv::Op::OpAtomicIIncrement: | |
| case spv::Op::OpAtomicIDecrement: | |
| case spv::Op::OpAtomicIAdd: | |
| case spv::Op::OpAtomicISub: | |
| case spv::Op::OpAtomicSMin: | |
| case spv::Op::OpAtomicUMin: | |
| case spv::Op::OpAtomicSMax: | |
| case spv::Op::OpAtomicUMax: | |
| case spv::Op::OpAtomicAnd: | |
| case spv::Op::OpAtomicOr: | |
| case spv::Op::OpAtomicXor: | |
| case spv::Op::OpAtomicFlagTestAndSet: | |
| return inst.GetSingleWordInOperand(0); | |
| case spv::Op::OpCopyMemory: | |
| return inst.GetSingleWordInOperand(1); | |
| case spv::Op::OpExtInst: { | |
| assert(GetExtensionSet(ir_context, inst) == kExtensionSetName && | |
| "Extension set is not supported"); | |
| switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) { | |
| case GLSLstd450InterpolateAtCentroid: | |
| case GLSLstd450InterpolateAtOffset: | |
| case GLSLstd450InterpolateAtSample: | |
| return inst.GetSingleWordInOperand(2); | |
| default: | |
| // This assertion will fail if not all memory read extension | |
| // instructions are handled in the switch. | |
| assert(false && "Not all memory opcodes are handled"); | |
| return 0; | |
| } | |
| } | |
| default: | |
| // This assertion will fail if not all memory read opcodes are handled in | |
| // the switch. | |
| assert(false && "Not all memory opcodes are handled"); | |
| return 0; | |
| } | |
| } | |
| bool TransformationMoveInstructionDown::IsMemoryWriteInstruction( | |
| opt::IRContext* ir_context, const opt::Instruction& inst) { | |
| switch (inst.opcode()) { | |
| // Simple Instructions. | |
| case spv::Op::OpStore: | |
| case spv::Op::OpCopyMemory: | |
| // Image instructions. | |
| case spv::Op::OpImageWrite: | |
| // Atomic instructions. | |
| case spv::Op::OpAtomicStore: | |
| case spv::Op::OpAtomicExchange: | |
| case spv::Op::OpAtomicCompareExchange: | |
| case spv::Op::OpAtomicCompareExchangeWeak: | |
| case spv::Op::OpAtomicIIncrement: | |
| case spv::Op::OpAtomicIDecrement: | |
| case spv::Op::OpAtomicIAdd: | |
| case spv::Op::OpAtomicISub: | |
| case spv::Op::OpAtomicSMin: | |
| case spv::Op::OpAtomicUMin: | |
| case spv::Op::OpAtomicSMax: | |
| case spv::Op::OpAtomicUMax: | |
| case spv::Op::OpAtomicAnd: | |
| case spv::Op::OpAtomicOr: | |
| case spv::Op::OpAtomicXor: | |
| case spv::Op::OpAtomicFlagTestAndSet: | |
| case spv::Op::OpAtomicFlagClear: | |
| return true; | |
| // Extensions. | |
| case spv::Op::OpExtInst: { | |
| if (GetExtensionSet(ir_context, inst) != kExtensionSetName) { | |
| return false; | |
| } | |
| auto extension = static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1)); | |
| return extension == GLSLstd450Modf || extension == GLSLstd450Frexp; | |
| } | |
| default: | |
| return false; | |
| } | |
| } | |
| uint32_t TransformationMoveInstructionDown::GetMemoryWriteTarget( | |
| opt::IRContext* ir_context, const opt::Instruction& inst) { | |
| (void)ir_context; // |ir_context| is only used in assertions. | |
| assert(IsMemoryWriteInstruction(ir_context, inst) && | |
| "|inst| is not a memory write instruction"); | |
| switch (inst.opcode()) { | |
| case spv::Op::OpStore: | |
| case spv::Op::OpCopyMemory: | |
| case spv::Op::OpImageWrite: | |
| case spv::Op::OpAtomicStore: | |
| case spv::Op::OpAtomicExchange: | |
| case spv::Op::OpAtomicCompareExchange: | |
| case spv::Op::OpAtomicCompareExchangeWeak: | |
| case spv::Op::OpAtomicIIncrement: | |
| case spv::Op::OpAtomicIDecrement: | |
| case spv::Op::OpAtomicIAdd: | |
| case spv::Op::OpAtomicISub: | |
| case spv::Op::OpAtomicSMin: | |
| case spv::Op::OpAtomicUMin: | |
| case spv::Op::OpAtomicSMax: | |
| case spv::Op::OpAtomicUMax: | |
| case spv::Op::OpAtomicAnd: | |
| case spv::Op::OpAtomicOr: | |
| case spv::Op::OpAtomicXor: | |
| case spv::Op::OpAtomicFlagTestAndSet: | |
| case spv::Op::OpAtomicFlagClear: | |
| return inst.GetSingleWordInOperand(0); | |
| case spv::Op::OpExtInst: { | |
| assert(GetExtensionSet(ir_context, inst) == kExtensionSetName && | |
| "Extension set is not supported"); | |
| switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) { | |
| case GLSLstd450Modf: | |
| case GLSLstd450Frexp: | |
| return inst.GetSingleWordInOperand(3); | |
| default: | |
| // This assertion will fail if not all memory write extension | |
| // instructions are handled in the switch. | |
| assert(false && "Not all opcodes are handled"); | |
| return 0; | |
| } | |
| } | |
| default: | |
| // This assertion will fail if not all memory write opcodes are handled in | |
| // the switch. | |
| assert(false && "Not all opcodes are handled"); | |
| return 0; | |
| } | |
| } | |
| bool TransformationMoveInstructionDown::IsMemoryInstruction( | |
| opt::IRContext* ir_context, const opt::Instruction& inst) { | |
| return IsMemoryReadInstruction(ir_context, inst) || | |
| IsMemoryWriteInstruction(ir_context, inst); | |
| } | |
| bool TransformationMoveInstructionDown::IsBarrierInstruction( | |
| const opt::Instruction& inst) { | |
| switch (inst.opcode()) { | |
| case spv::Op::OpMemoryBarrier: | |
| case spv::Op::OpControlBarrier: | |
| case spv::Op::OpMemoryNamedBarrier: | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| bool TransformationMoveInstructionDown::CanSafelySwapInstructions( | |
| opt::IRContext* ir_context, const opt::Instruction& a, | |
| const opt::Instruction& b, const FactManager& fact_manager) { | |
| assert(IsInstructionSupported(ir_context, a) && | |
| IsInstructionSupported(ir_context, b) && | |
| "Both opcodes must be supported"); | |
| // One of opcodes is simple - we can swap them without any side-effects. | |
| if (IsSimpleInstruction(ir_context, a) || | |
| IsSimpleInstruction(ir_context, b)) { | |
| return true; | |
| } | |
| // Both parameters are either memory instruction or barriers. | |
| // One of the opcodes is a barrier - can't swap them. | |
| if (IsBarrierInstruction(a) || IsBarrierInstruction(b)) { | |
| return false; | |
| } | |
| // Both parameters are memory instructions. | |
| // Both parameters only read from memory - it's OK to swap them. | |
| if (!IsMemoryWriteInstruction(ir_context, a) && | |
| !IsMemoryWriteInstruction(ir_context, b)) { | |
| return true; | |
| } | |
| // At least one of parameters is a memory read instruction. | |
| // In theory, we can swap two memory instructions, one of which reads | |
| // from the memory, if the read target (the pointer the memory is read from) | |
| // and the write target (the memory is written into): | |
| // - point to different memory regions | |
| // - point to the same region with irrelevant value | |
| // - point to the same region and the region is not used anymore. | |
| // | |
| // However, we can't currently determine if two pointers point to two | |
| // different memory regions. That being said, if two pointers are not | |
| // synonymous, they still might point to the same memory region. For example: | |
| // %1 = OpVariable ... | |
| // %2 = OpAccessChain %1 0 | |
| // %3 = OpAccessChain %1 0 | |
| // In this pseudo-code, %2 and %3 are not synonymous but point to the same | |
| // memory location. This implies that we can't determine if some memory | |
| // location is not used in the block. | |
| // | |
| // With this in mind, consider two cases (we will build a table for each one): | |
| // - one instruction only reads from memory, the other one only writes to it. | |
| // S - both point to the same memory region. | |
| // D - both point to different memory regions. | |
| // 0, 1, 2 - neither, one of or both of the memory regions are irrelevant. | |
| // |-| - can't swap; |+| - can swap. | |
| // | 0 | 1 | 2 | | |
| // S : - + + | |
| // D : + + + | |
| // - both instructions write to memory. Notation is the same. | |
| // | 0 | 1 | 2 | | |
| // S : * + + | |
| // D : + + + | |
| // * - we can swap two instructions that write into the same non-irrelevant | |
| // memory region if the written value is the same. | |
| // | |
| // Note that we can't always distinguish between S and D. Also note that | |
| // in case of S, if one of the instructions is marked with | |
| // PointeeValueIsIrrelevant, then the pointee of the other one is irrelevant | |
| // as well even if the instruction is not marked with that fact. | |
| // | |
| // TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3723): | |
| // This procedure can be improved when we can determine if two pointers point | |
| // to different memory regions. | |
| // From now on we will denote an instruction that: | |
| // - only reads from memory - R | |
| // - only writes into memory - W | |
| // - reads and writes - RW | |
| // | |
| // Both |a| and |b| can be either W or RW at this point. Additionally, at most | |
| // one of them can be R. The procedure below checks all possible combinations | |
| // of R, W and RW according to the tables above. We conservatively assume that | |
| // both |a| and |b| point to the same memory region. | |
| auto memory_is_irrelevant = [ir_context, &fact_manager](uint32_t id) { | |
| const auto* inst = ir_context->get_def_use_mgr()->GetDef(id); | |
| if (!inst->type_id()) { | |
| return false; | |
| } | |
| const auto* type = ir_context->get_type_mgr()->GetType(inst->type_id()); | |
| assert(type && "|id| has invalid type"); | |
| if (!type->AsPointer()) { | |
| return false; | |
| } | |
| return fact_manager.PointeeValueIsIrrelevant(id); | |
| }; | |
| if (IsMemoryWriteInstruction(ir_context, a) && | |
| IsMemoryWriteInstruction(ir_context, b) && | |
| (memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) || | |
| memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b)))) { | |
| // We ignore the case when the written value is the same. This is because | |
| // the written value might not be equal to any of the instruction's | |
| // operands. | |
| return true; | |
| } | |
| if (IsMemoryReadInstruction(ir_context, a) && | |
| IsMemoryWriteInstruction(ir_context, b) && | |
| !memory_is_irrelevant(GetMemoryReadTarget(ir_context, a)) && | |
| !memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b))) { | |
| return false; | |
| } | |
| if (IsMemoryWriteInstruction(ir_context, a) && | |
| IsMemoryReadInstruction(ir_context, b) && | |
| !memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) && | |
| !memory_is_irrelevant(GetMemoryReadTarget(ir_context, b))) { | |
| return false; | |
| } | |
| return IsMemoryReadInstruction(ir_context, a) || | |
| IsMemoryReadInstruction(ir_context, b); | |
| } | |
| std::unordered_set<uint32_t> TransformationMoveInstructionDown::GetFreshIds() | |
| const { | |
| return std::unordered_set<uint32_t>(); | |
| } | |
| } // namespace fuzz | |
| } // namespace spvtools |