mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-10-18 11:10:05 +00:00
spirv-fuzz: Support memory instructions MoveInstructionDown (#3700)
Part of #3605.
This commit is contained in:
parent
1e1c308ded
commit
4c239bd81b
@ -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 @@ TransformationMoveInstructionDown::TransformationMoveInstructionDown(
|
||||
}
|
||||
|
||||
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 @@ bool TransformationMoveInstructionDown::IsApplicable(
|
||||
}
|
||||
|
||||
// Instruction's opcode must be supported by this transformation.
|
||||
if (!IsOpcodeSupported(inst->opcode())) {
|
||||
if (!IsInstructionSupported(ir_context, *inst)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -56,6 +74,20 @@ bool TransformationMoveInstructionDown::IsApplicable(
|
||||
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 @@ bool TransformationMoveInstructionDown::IsApplicable(
|
||||
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 @@ protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const {
|
||||
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 @@ bool TransformationMoveInstructionDown::IsOpcodeSupported(SpvOp opcode) {
|
||||
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
|
||||
|
@ -49,9 +49,52 @@ class TransformationMoveInstructionDown : public Transformation {
|
||||
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_;
|
||||
};
|
||||
|
@ -50,10 +50,15 @@ TEST(TransformationMoveInstructionDownTest, BasicTest) {
|
||||
%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 @@ TEST(TransformationMoveInstructionDownTest, BasicTest) {
|
||||
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 @@ TEST(TransformationMoveInstructionDownTest, BasicTest) {
|
||||
%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
|
||||
|
Loading…
Reference in New Issue
Block a user