spirv-fuzz: Support memory instructions MoveInstructionDown (#3700)
Part of #3605.
diff --git a/source/fuzz/transformation_move_instruction_down.cpp b/source/fuzz/transformation_move_instruction_down.cpp
index 7e38354..7069d07 100644
--- a/source/fuzz/transformation_move_instruction_down.cpp
+++ b/source/fuzz/transformation_move_instruction_down.cpp
@@ -14,11 +14,28 @@
#include "source/fuzz/transformation_move_instruction_down.h"
+#include "external/spirv-headers/include/spirv/unified1/GLSL.std.450.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.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() == SpvOpExtInst && "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(
const protobufs::TransformationMoveInstructionDown& message)
@@ -30,7 +47,8 @@
}
bool TransformationMoveInstructionDown::IsApplicable(
- opt::IRContext* ir_context, const TransformationContext& /*unused*/) const {
+ opt::IRContext* ir_context,
+ const TransformationContext& transformation_context) const {
// |instruction| must be valid.
auto* inst = FindInstruction(message_.instruction(), ir_context);
if (!inst) {
@@ -38,7 +56,7 @@
}
// Instruction's opcode must be supported by this transformation.
- if (!IsOpcodeSupported(inst->opcode())) {
+ if (!IsInstructionSupported(ir_context, *inst)) {
return false;
}
@@ -56,6 +74,20 @@
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() ||
@@ -68,7 +100,7 @@
if (inst->result_id()) {
for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) {
const auto& operand = successor_it->GetInOperand(i);
- if (operand.type == SPV_OPERAND_TYPE_ID &&
+ if (spvIsInIdType(operand.type) &&
operand.words[0] == inst->result_id()) {
return false;
}
@@ -99,17 +131,24 @@
return result;
}
-bool TransformationMoveInstructionDown::IsOpcodeSupported(SpvOp opcode) {
+bool TransformationMoveInstructionDown::IsInstructionSupported(
+ opt::IRContext* ir_context, const opt::Instruction& inst) {
// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3605):
- // We only support "simple" instructions that work don't with memory.
- // We should extend this so that we support the ones that modify the memory
- // too.
- switch (opcode) {
+ // 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 SpvOpNop:
case SpvOpUndef:
case SpvOpAccessChain:
case SpvOpInBoundsAccessChain:
- case SpvOpArrayLength:
+ // OpAccessChain and OpInBoundsAccessChain are considered simple
+ // instructions since they result in a pointer to the object in memory,
+ // not the object itself.
case SpvOpVectorExtractDynamic:
case SpvOpVectorInsertDynamic:
case SpvOpVectorShuffle:
@@ -207,13 +246,484 @@
case SpvOpBitReverse:
case SpvOpBitCount:
case SpvOpCopyLogical:
- case SpvOpPtrEqual:
- case SpvOpPtrNotEqual:
+ return true;
+ case SpvOpExtInst: {
+ 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 SpvOpLoad:
+ case SpvOpCopyMemory:
+ // Image instructions.
+ case SpvOpImageSampleImplicitLod:
+ case SpvOpImageSampleExplicitLod:
+ case SpvOpImageSampleDrefImplicitLod:
+ case SpvOpImageSampleDrefExplicitLod:
+ case SpvOpImageSampleProjImplicitLod:
+ case SpvOpImageSampleProjExplicitLod:
+ case SpvOpImageSampleProjDrefImplicitLod:
+ case SpvOpImageSampleProjDrefExplicitLod:
+ case SpvOpImageFetch:
+ case SpvOpImageGather:
+ case SpvOpImageDrefGather:
+ case SpvOpImageRead:
+ case SpvOpImageSparseSampleImplicitLod:
+ case SpvOpImageSparseSampleExplicitLod:
+ case SpvOpImageSparseSampleDrefImplicitLod:
+ case SpvOpImageSparseSampleDrefExplicitLod:
+ case SpvOpImageSparseSampleProjImplicitLod:
+ case SpvOpImageSparseSampleProjExplicitLod:
+ case SpvOpImageSparseSampleProjDrefImplicitLod:
+ case SpvOpImageSparseSampleProjDrefExplicitLod:
+ case SpvOpImageSparseFetch:
+ case SpvOpImageSparseGather:
+ case SpvOpImageSparseDrefGather:
+ case SpvOpImageSparseRead:
+ // Atomic instructions.
+ case SpvOpAtomicLoad:
+ case SpvOpAtomicExchange:
+ case SpvOpAtomicCompareExchange:
+ case SpvOpAtomicCompareExchangeWeak:
+ case SpvOpAtomicIIncrement:
+ case SpvOpAtomicIDecrement:
+ case SpvOpAtomicIAdd:
+ case SpvOpAtomicISub:
+ case SpvOpAtomicSMin:
+ case SpvOpAtomicUMin:
+ case SpvOpAtomicSMax:
+ case SpvOpAtomicUMax:
+ case SpvOpAtomicAnd:
+ case SpvOpAtomicOr:
+ case SpvOpAtomicXor:
+ case SpvOpAtomicFlagTestAndSet:
+ return true;
+ // Extensions.
+ case SpvOpExtInst: {
+ 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 SpvOpLoad:
+ // Image instructions.
+ case SpvOpImageSampleImplicitLod:
+ case SpvOpImageSampleExplicitLod:
+ case SpvOpImageSampleDrefImplicitLod:
+ case SpvOpImageSampleDrefExplicitLod:
+ case SpvOpImageSampleProjImplicitLod:
+ case SpvOpImageSampleProjExplicitLod:
+ case SpvOpImageSampleProjDrefImplicitLod:
+ case SpvOpImageSampleProjDrefExplicitLod:
+ case SpvOpImageFetch:
+ case SpvOpImageGather:
+ case SpvOpImageDrefGather:
+ case SpvOpImageRead:
+ case SpvOpImageSparseSampleImplicitLod:
+ case SpvOpImageSparseSampleExplicitLod:
+ case SpvOpImageSparseSampleDrefImplicitLod:
+ case SpvOpImageSparseSampleDrefExplicitLod:
+ case SpvOpImageSparseSampleProjImplicitLod:
+ case SpvOpImageSparseSampleProjExplicitLod:
+ case SpvOpImageSparseSampleProjDrefImplicitLod:
+ case SpvOpImageSparseSampleProjDrefExplicitLod:
+ case SpvOpImageSparseFetch:
+ case SpvOpImageSparseGather:
+ case SpvOpImageSparseDrefGather:
+ case SpvOpImageSparseRead:
+ // Atomic instructions.
+ case SpvOpAtomicLoad:
+ case SpvOpAtomicExchange:
+ case SpvOpAtomicCompareExchange:
+ case SpvOpAtomicCompareExchangeWeak:
+ case SpvOpAtomicIIncrement:
+ case SpvOpAtomicIDecrement:
+ case SpvOpAtomicIAdd:
+ case SpvOpAtomicISub:
+ case SpvOpAtomicSMin:
+ case SpvOpAtomicUMin:
+ case SpvOpAtomicSMax:
+ case SpvOpAtomicUMax:
+ case SpvOpAtomicAnd:
+ case SpvOpAtomicOr:
+ case SpvOpAtomicXor:
+ case SpvOpAtomicFlagTestAndSet:
+ return inst.GetSingleWordInOperand(0);
+ case SpvOpCopyMemory:
+ return inst.GetSingleWordInOperand(1);
+ case SpvOpExtInst: {
+ 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 SpvOpStore:
+ case SpvOpCopyMemory:
+ // Image instructions.
+ case SpvOpImageWrite:
+ // Atomic instructions.
+ case SpvOpAtomicStore:
+ case SpvOpAtomicExchange:
+ case SpvOpAtomicCompareExchange:
+ case SpvOpAtomicCompareExchangeWeak:
+ case SpvOpAtomicIIncrement:
+ case SpvOpAtomicIDecrement:
+ case SpvOpAtomicIAdd:
+ case SpvOpAtomicISub:
+ case SpvOpAtomicSMin:
+ case SpvOpAtomicUMin:
+ case SpvOpAtomicSMax:
+ case SpvOpAtomicUMax:
+ case SpvOpAtomicAnd:
+ case SpvOpAtomicOr:
+ case SpvOpAtomicXor:
+ case SpvOpAtomicFlagTestAndSet:
+ case SpvOpAtomicFlagClear:
+ return true;
+ // Extensions.
+ case SpvOpExtInst: {
+ 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 SpvOpStore:
+ case SpvOpCopyMemory:
+ case SpvOpImageWrite:
+ case SpvOpAtomicStore:
+ case SpvOpAtomicExchange:
+ case SpvOpAtomicCompareExchange:
+ case SpvOpAtomicCompareExchangeWeak:
+ case SpvOpAtomicIIncrement:
+ case SpvOpAtomicIDecrement:
+ case SpvOpAtomicIAdd:
+ case SpvOpAtomicISub:
+ case SpvOpAtomicSMin:
+ case SpvOpAtomicUMin:
+ case SpvOpAtomicSMax:
+ case SpvOpAtomicUMax:
+ case SpvOpAtomicAnd:
+ case SpvOpAtomicOr:
+ case SpvOpAtomicXor:
+ case SpvOpAtomicFlagTestAndSet:
+ case SpvOpAtomicFlagClear:
+ return inst.GetSingleWordInOperand(0);
+ case SpvOpExtInst: {
+ 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 SpvOpMemoryBarrier:
+ case SpvOpControlBarrier:
+ case SpvOpMemoryNamedBarrier:
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);
+}
+
} // namespace fuzz
} // namespace spvtools
diff --git a/source/fuzz/transformation_move_instruction_down.h b/source/fuzz/transformation_move_instruction_down.h
index 3f3c302..8b1e5ed 100644
--- a/source/fuzz/transformation_move_instruction_down.h
+++ b/source/fuzz/transformation_move_instruction_down.h
@@ -49,9 +49,52 @@
protobufs::Transformation ToMessage() const override;
private:
- // Returns true if the |opcode| is supported by this transformation (i.e.
- // we can move an instruction with this opcode).
- static bool IsOpcodeSupported(SpvOp opcode);
+ // Returns true if the |inst| is supported by this transformation.
+ static bool IsInstructionSupported(opt::IRContext* ir_context,
+ const opt::Instruction& inst);
+
+ // Returns true if |inst| represents a "simple" instruction. That is, it
+ // neither reads from nor writes to the memory and is not a barrier.
+ static bool IsSimpleInstruction(opt::IRContext* ir_context,
+ const opt::Instruction& inst);
+
+ // Returns true if |inst| reads from memory.
+ static bool IsMemoryReadInstruction(opt::IRContext* ir_context,
+ const opt::Instruction& inst);
+
+ // Returns id being used by |inst| to read from. |inst| must be a memory read
+ // instruction (see IsMemoryReadInstruction). Returned id is not guaranteed to
+ // have pointer type.
+ static uint32_t GetMemoryReadTarget(opt::IRContext* ir_context,
+ const opt::Instruction& inst);
+
+ // Returns true if |inst| that writes to the memory.
+ static bool IsMemoryWriteInstruction(opt::IRContext* ir_context,
+ const opt::Instruction& inst);
+
+ // Returns id being used by |inst| to write into. |inst| must be a memory
+ // write instruction (see IsMemoryWriteInstruction). Returned id is not
+ // guaranteed to have pointer type.
+ static uint32_t GetMemoryWriteTarget(opt::IRContext* ir_context,
+ const opt::Instruction& inst);
+
+ // Returns true if |inst| either reads from or writes to the memory
+ // (see IsMemoryReadInstruction and IsMemoryWriteInstruction accordingly).
+ static bool IsMemoryInstruction(opt::IRContext* ir_context,
+ const opt::Instruction& inst);
+
+ // Returns true if |inst| is a barrier instruction.
+ static bool IsBarrierInstruction(const opt::Instruction& inst);
+
+ // Returns true if it is possible to swap |a| and |b| without changing the
+ // module's semantics. |a| and |b| are required to be supported instructions
+ // (see IsInstructionSupported). In particular, if either |a| or |b| are
+ // memory or barrier instructions, some checks are used to only say that they
+ // can be swapped if the swap is definitely semantics-preserving.
+ static bool CanSafelySwapInstructions(opt::IRContext* ir_context,
+ const opt::Instruction& a,
+ const opt::Instruction& b,
+ const FactManager& fact_manager);
protobufs::TransformationMoveInstructionDown message_;
};
diff --git a/test/fuzz/transformation_move_instruction_down_test.cpp b/test/fuzz/transformation_move_instruction_down_test.cpp
index 088938e..b49cfd2 100644
--- a/test/fuzz/transformation_move_instruction_down_test.cpp
+++ b/test/fuzz/transformation_move_instruction_down_test.cpp
@@ -50,10 +50,15 @@
%18 = OpLabel
OpBranch %19
%19 = OpLabel
+ %42 = OpFunctionCall %2 %40
%22 = OpIAdd %6 %15 %15
%21 = OpIAdd %6 %15 %15
OpReturn
OpFunctionEnd
+ %40 = OpFunction %2 None %3
+ %41 = OpLabel
+ OpReturn
+ OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
@@ -79,10 +84,7 @@
MakeInstructionDescriptor(12, SpvOpVariable, 0))
.IsApplicable(context.get(), transformation_context));
ASSERT_FALSE(TransformationMoveInstructionDown(
- MakeInstructionDescriptor(11, SpvOpStore, 0))
- .IsApplicable(context.get(), transformation_context));
- ASSERT_FALSE(TransformationMoveInstructionDown(
- MakeInstructionDescriptor(14, SpvOpLoad, 0))
+ MakeInstructionDescriptor(42, SpvOpFunctionCall, 0))
.IsApplicable(context.get(), transformation_context));
// Can't move the last instruction in the block.
@@ -151,15 +153,554 @@
%18 = OpLabel
OpBranch %19
%19 = OpLabel
+ %42 = OpFunctionCall %2 %40
%21 = OpIAdd %6 %15 %15
%22 = OpIAdd %6 %15 %15
OpReturn
OpFunctionEnd
+ %40 = OpFunction %2 None %3
+ %41 = OpLabel
+ OpReturn
+ OpFunctionEnd
)";
ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
}
+TEST(TransformationMoveInstructionDownTest, HandlesUnsupportedInstructions) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 16 1 1
+ OpSource ESSL 320
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 2
+ %20 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %21 = OpVariable %20 Function %7
+
+ ; can swap simple and not supported instructions
+ %8 = OpCopyObject %6 %7
+ %9 = OpFunctionCall %2 %12
+
+ ; cannot swap memory and not supported instruction
+ %22 = OpLoad %6 %21
+ %23 = OpFunctionCall %2 %12
+
+ ; cannot swap barrier and not supported instruction
+ OpMemoryBarrier %7 %7
+ %24 = OpFunctionCall %2 %12
+
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Swap memory instruction with an unsupported one.
+ ASSERT_FALSE(TransformationMoveInstructionDown(
+ MakeInstructionDescriptor(22, SpvOpLoad, 0))
+ .IsApplicable(context.get(), transformation_context));
+
+ // Swap memory barrier with an unsupported one.
+ ASSERT_FALSE(TransformationMoveInstructionDown(
+ MakeInstructionDescriptor(23, SpvOpMemoryBarrier, 0))
+ .IsApplicable(context.get(), transformation_context));
+
+ // Swap simple instruction with an unsupported one.
+ TransformationMoveInstructionDown transformation(
+ MakeInstructionDescriptor(8, SpvOpCopyObject, 0));
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ std::string after_transformation = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 16 1 1
+ OpSource ESSL 320
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 2
+ %20 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %21 = OpVariable %20 Function %7
+
+ ; can swap simple and not supported instructions
+ %9 = OpFunctionCall %2 %12
+ %8 = OpCopyObject %6 %7
+
+ ; cannot swap memory and not supported instruction
+ %22 = OpLoad %6 %21
+ %23 = OpFunctionCall %2 %12
+
+ ; cannot swap barrier and not supported instruction
+ OpMemoryBarrier %7 %7
+ %24 = OpFunctionCall %2 %12
+
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMoveInstructionDownTest, HandlesBarrierInstructions) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 16 1 1
+ OpSource ESSL 320
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 2
+ %20 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %21 = OpVariable %20 Function %7
+
+ ; cannot swap two barrier instructions
+ OpMemoryBarrier %7 %7
+ OpMemoryBarrier %7 %7
+
+ ; cannot swap barrier and memory instructions
+ OpMemoryBarrier %7 %7
+ %22 = OpLoad %6 %21
+ OpMemoryBarrier %7 %7
+
+ ; can swap barrier and simple instructions
+ %23 = OpCopyObject %6 %7
+ OpMemoryBarrier %7 %7
+
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Swap two barrier instructions.
+ ASSERT_FALSE(TransformationMoveInstructionDown(
+ MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 0))
+ .IsApplicable(context.get(), transformation_context));
+
+ // Swap barrier and memory instructions.
+ ASSERT_FALSE(TransformationMoveInstructionDown(
+ MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 2))
+ .IsApplicable(context.get(), transformation_context));
+ ASSERT_FALSE(TransformationMoveInstructionDown(
+ MakeInstructionDescriptor(22, SpvOpLoad, 0))
+ .IsApplicable(context.get(), transformation_context));
+
+ // Swap barrier and simple instructions.
+ {
+ TransformationMoveInstructionDown transformation(
+ MakeInstructionDescriptor(23, SpvOpCopyObject, 0));
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+ {
+ TransformationMoveInstructionDown transformation(
+ MakeInstructionDescriptor(22, SpvOpMemoryBarrier, 1));
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+
+ ASSERT_TRUE(IsEqual(env, shader, context.get()));
+}
+
+TEST(TransformationMoveInstructionDownTest, HandlesSimpleInstructions) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 16 1 1
+ OpSource ESSL 320
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 2
+ %20 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %21 = OpVariable %20 Function %7
+
+ ; can swap simple and barrier instructions
+ %40 = OpCopyObject %6 %7
+ OpMemoryBarrier %7 %7
+
+ ; can swap simple and memory instructions
+ %41 = OpCopyObject %6 %7
+ %22 = OpLoad %6 %21
+
+ ; can swap two simple instructions
+ %23 = OpCopyObject %6 %7
+ %42 = OpCopyObject %6 %7
+
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ // Swap simple and barrier instructions.
+ {
+ TransformationMoveInstructionDown transformation(
+ MakeInstructionDescriptor(40, SpvOpCopyObject, 0));
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+ {
+ TransformationMoveInstructionDown transformation(
+ MakeInstructionDescriptor(21, SpvOpMemoryBarrier, 0));
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+
+ // Swap simple and memory instructions.
+ {
+ TransformationMoveInstructionDown transformation(
+ MakeInstructionDescriptor(41, SpvOpCopyObject, 0));
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+ {
+ TransformationMoveInstructionDown transformation(
+ MakeInstructionDescriptor(22, SpvOpLoad, 0));
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+
+ // Swap two simple instructions.
+ {
+ TransformationMoveInstructionDown transformation(
+ MakeInstructionDescriptor(23, SpvOpCopyObject, 0));
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+
+ std::string after_transformation = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 16 1 1
+ OpSource ESSL 320
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 2
+ %20 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %21 = OpVariable %20 Function %7
+
+ ; can swap simple and barrier instructions
+ %40 = OpCopyObject %6 %7
+ OpMemoryBarrier %7 %7
+
+ ; can swap simple and memory instructions
+ %41 = OpCopyObject %6 %7
+ %22 = OpLoad %6 %21
+
+ ; can swap two simple instructions
+ %42 = OpCopyObject %6 %7
+ %23 = OpCopyObject %6 %7
+
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %2 None %3
+ %13 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+TEST(TransformationMoveInstructionDownTest, HandlesMemoryInstructions) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %4 "main"
+ OpExecutionMode %4 LocalSize 16 1 1
+ OpSource ESSL 320
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeInt 32 0
+ %7 = OpConstant %6 2
+ %20 = OpTypePointer Function %6
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %21 = OpVariable %20 Function %7
+ %22 = OpVariable %20 Function %7
+
+ ; swap R and R instructions
+ %23 = OpLoad %6 %21
+ %24 = OpLoad %6 %22
+
+ ; swap R and RW instructions
+
+ ; can't swap
+ %25 = OpLoad %6 %21
+ OpCopyMemory %21 %22
+
+ ; can swap
+ %26 = OpLoad %6 %21
+ OpCopyMemory %22 %21
+
+ %27 = OpLoad %6 %22
+ OpCopyMemory %21 %22
+
+ %28 = OpLoad %6 %22
+ OpCopyMemory %22 %21
+
+ ; swap R and W instructions
+
+ ; can't swap
+ %29 = OpLoad %6 %21
+ OpStore %21 %7
+
+ ; can swap
+ %30 = OpLoad %6 %22
+ OpStore %21 %7
+
+ %31 = OpLoad %6 %21
+ OpStore %22 %7
+
+ %32 = OpLoad %6 %22
+ OpStore %22 %7
+
+ ; swap RW and RW instructions
+
+ ; can't swap
+ OpCopyMemory %21 %21
+ OpCopyMemory %21 %21
+
+ OpCopyMemory %21 %22
+ OpCopyMemory %21 %21
+
+ OpCopyMemory %21 %21
+ OpCopyMemory %21 %22
+
+ ; can swap
+ OpCopyMemory %22 %21
+ OpCopyMemory %21 %22
+
+ OpCopyMemory %22 %21
+ OpCopyMemory %22 %21
+
+ OpCopyMemory %21 %22
+ OpCopyMemory %21 %22
+
+ ; swap RW and W instructions
+
+ ; can't swap
+ OpCopyMemory %21 %21
+ OpStore %21 %7
+
+ OpStore %21 %7
+ OpCopyMemory %21 %21
+
+ ; can swap
+ OpCopyMemory %22 %21
+ OpStore %21 %7
+
+ OpCopyMemory %21 %22
+ OpStore %21 %7
+
+ OpCopyMemory %21 %21
+ OpStore %22 %7
+
+ ; swap W and W instructions
+
+ ; can't swap
+ OpStore %21 %7
+ OpStore %21 %7
+
+ ; can swap
+ OpStore %22 %7
+ OpStore %21 %7
+
+ OpStore %22 %7
+ OpStore %22 %7
+
+ OpReturn
+ OpFunctionEnd
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+ spvtools::ValidatorOptions validator_options;
+ TransformationContext transformation_context(&fact_manager,
+ validator_options);
+
+ fact_manager.AddFactValueOfPointeeIsIrrelevant(22);
+
+ // Invalid swaps.
+
+ protobufs::InstructionDescriptor invalid_swaps[] = {
+ // R and RW
+ MakeInstructionDescriptor(25, SpvOpLoad, 0),
+
+ // R and W
+ MakeInstructionDescriptor(29, SpvOpLoad, 0),
+
+ // RW and RW
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 0),
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 2),
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 4),
+
+ // RW and W
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 12),
+ MakeInstructionDescriptor(32, SpvOpStore, 1),
+
+ // W and W
+ MakeInstructionDescriptor(32, SpvOpStore, 6),
+ };
+
+ for (const auto& descriptor : invalid_swaps) {
+ ASSERT_FALSE(TransformationMoveInstructionDown(descriptor)
+ .IsApplicable(context.get(), transformation_context));
+ }
+
+ // Valid swaps.
+ protobufs::InstructionDescriptor valid_swaps[] = {
+ // R and R
+ MakeInstructionDescriptor(23, SpvOpLoad, 0),
+ MakeInstructionDescriptor(24, SpvOpLoad, 0),
+
+ // R and RW
+ MakeInstructionDescriptor(26, SpvOpLoad, 0),
+ MakeInstructionDescriptor(25, SpvOpCopyMemory, 1),
+
+ MakeInstructionDescriptor(27, SpvOpLoad, 0),
+ MakeInstructionDescriptor(26, SpvOpCopyMemory, 1),
+
+ MakeInstructionDescriptor(28, SpvOpLoad, 0),
+ MakeInstructionDescriptor(27, SpvOpCopyMemory, 1),
+
+ // R and W
+ MakeInstructionDescriptor(30, SpvOpLoad, 0),
+ MakeInstructionDescriptor(29, SpvOpStore, 1),
+
+ MakeInstructionDescriptor(31, SpvOpLoad, 0),
+ MakeInstructionDescriptor(30, SpvOpStore, 1),
+
+ MakeInstructionDescriptor(32, SpvOpLoad, 0),
+ MakeInstructionDescriptor(31, SpvOpStore, 1),
+
+ // RW and RW
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 6),
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 6),
+
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 8),
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 8),
+
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 10),
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 10),
+
+ // RW and W
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 14),
+ MakeInstructionDescriptor(32, SpvOpStore, 3),
+
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 15),
+ MakeInstructionDescriptor(32, SpvOpStore, 4),
+
+ MakeInstructionDescriptor(32, SpvOpCopyMemory, 16),
+ MakeInstructionDescriptor(32, SpvOpStore, 5),
+
+ // W and W
+ MakeInstructionDescriptor(32, SpvOpStore, 8),
+ MakeInstructionDescriptor(32, SpvOpStore, 8),
+
+ MakeInstructionDescriptor(32, SpvOpStore, 10),
+ MakeInstructionDescriptor(32, SpvOpStore, 10),
+ };
+
+ for (const auto& descriptor : valid_swaps) {
+ TransformationMoveInstructionDown transformation(descriptor);
+ ASSERT_TRUE(
+ transformation.IsApplicable(context.get(), transformation_context));
+ transformation.Apply(context.get(), &transformation_context);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+
+ ASSERT_TRUE(IsEqual(env, shader, context.get()));
+}
+
} // namespace
} // namespace fuzz
} // namespace spvtools