spirv-fuzz: Support memory instructions MoveInstructionDown (#3700)

Part of #3605.
This commit is contained in:
Vasyl Teliman 2020-09-15 17:04:39 +03:00 committed by GitHub
parent 1e1c308ded
commit 4c239bd81b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 1112 additions and 18 deletions

View File

@ -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

View File

@ -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_;
};

View File

@ -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