#include "source/fuzz/transformation_move_instruction_down.h"
#include "source/fuzz/fuzzer_util.h"
#include "source/fuzz/instruction_descriptor.h"
#include "spirv/unified1/GLSL.std.450.h"
namespace spvtools {
namespace fuzz {
namespace {
const char* const kExtensionSetName = "GLSL.std.450";
std::string GetExtensionSet(opt::IRContext* ir_context,
const opt::Instruction& op_ext_inst) {
assert(op_ext_inst.opcode() == spv::Op::OpExtInst && "Wrong opcode");
const auto* ext_inst_import = ir_context->get_def_use_mgr()->GetDef(
op_ext_inst.GetSingleWordInOperand(0));
assert(ext_inst_import && "Extension set is not imported");
return ext_inst_import->GetInOperand(0).AsString();
}
}
TransformationMoveInstructionDown::TransformationMoveInstructionDown(
protobufs::TransformationMoveInstructionDown message)
: message_(std::move(message)) {}
TransformationMoveInstructionDown::TransformationMoveInstructionDown(
const protobufs::InstructionDescriptor& instruction) {
*message_.mutable_instruction() = instruction;
}
bool TransformationMoveInstructionDown::IsApplicable(
opt::IRContext* ir_context,
const TransformationContext& transformation_context) const {
auto* inst = FindInstruction(message_.instruction(), ir_context);
if (!inst) {
return false;
}
if (!IsInstructionSupported(ir_context, *inst)) {
return false;
}
auto* inst_block = ir_context->get_instr_block(inst);
assert(inst_block &&
"Global instructions and function parameters are not supported");
auto inst_it = fuzzerutil::GetIteratorForInstruction(inst_block, inst);
assert(inst_it != inst_block->end() &&
"Can't get an iterator for the instruction");
auto successor_it = ++inst_it;
if (successor_it == inst_block->end()) {
return false;
}
if (!IsSimpleInstruction(ir_context, *inst) &&
!IsInstructionSupported(ir_context, *successor_it)) {
return false;
}
if (IsInstructionSupported(ir_context, *successor_it) &&
!CanSafelySwapInstructions(ir_context, *inst, *successor_it,
*transformation_context.GetFactManager())) {
return false;
}
auto successors_successor_it = ++inst_it;
if (successors_successor_it == inst_block->end() ||
!fuzzerutil::CanInsertOpcodeBeforeInstruction(inst->opcode(),
successors_successor_it)) {
return false;
}
if (inst->result_id()) {
for (uint32_t i = 0; i < successor_it->NumInOperands(); ++i) {
const auto& operand = successor_it->GetInOperand(i);
if (spvIsInIdType(operand.type) &&
operand.words[0] == inst->result_id()) {
return false;
}
}
}
return true;
}
void TransformationMoveInstructionDown::Apply(
opt::IRContext* ir_context, TransformationContext* ) const {
auto* inst = FindInstruction(message_.instruction(), ir_context);
assert(inst &&
"The instruction should've been validated in the IsApplicable");
auto inst_it = fuzzerutil::GetIteratorForInstruction(
ir_context->get_instr_block(inst), inst);
inst->InsertAfter(&*++inst_it);
ir_context->InvalidateAnalyses(opt::IRContext::kAnalysisNone);
}
protobufs::Transformation TransformationMoveInstructionDown::ToMessage() const {
protobufs::Transformation result;
*result.mutable_move_instruction_down() = message_;
return result;
}
bool TransformationMoveInstructionDown::IsInstructionSupported(
opt::IRContext* ir_context, const opt::Instruction& inst) {
return IsSimpleInstruction(ir_context, inst) ||
IsMemoryInstruction(ir_context, inst) || IsBarrierInstruction(inst);
}
bool TransformationMoveInstructionDown::IsSimpleInstruction(
opt::IRContext* ir_context, const opt::Instruction& inst) {
switch (inst.opcode()) {
case spv::Op::OpNop:
case spv::Op::OpUndef:
case spv::Op::OpAccessChain:
case spv::Op::OpInBoundsAccessChain:
case spv::Op::OpVectorExtractDynamic:
case spv::Op::OpVectorInsertDynamic:
case spv::Op::OpVectorShuffle:
case spv::Op::OpCompositeConstruct:
case spv::Op::OpCompositeExtract:
case spv::Op::OpCompositeInsert:
case spv::Op::OpCopyObject:
case spv::Op::OpTranspose:
case spv::Op::OpConvertFToU:
case spv::Op::OpConvertFToS:
case spv::Op::OpConvertSToF:
case spv::Op::OpConvertUToF:
case spv::Op::OpUConvert:
case spv::Op::OpSConvert:
case spv::Op::OpFConvert:
case spv::Op::OpQuantizeToF16:
case spv::Op::OpSatConvertSToU:
case spv::Op::OpSatConvertUToS:
case spv::Op::OpBitcast:
case spv::Op::OpSNegate:
case spv::Op::OpFNegate:
case spv::Op::OpIAdd:
case spv::Op::OpFAdd:
case spv::Op::OpISub:
case spv::Op::OpFSub:
case spv::Op::OpIMul:
case spv::Op::OpFMul:
case spv::Op::OpUDiv:
case spv::Op::OpSDiv:
case spv::Op::OpFDiv:
case spv::Op::OpUMod:
case spv::Op::OpSRem:
case spv::Op::OpSMod:
case spv::Op::OpFRem:
case spv::Op::OpFMod:
case spv::Op::OpVectorTimesScalar:
case spv::Op::OpMatrixTimesScalar:
case spv::Op::OpVectorTimesMatrix:
case spv::Op::OpMatrixTimesVector:
case spv::Op::OpMatrixTimesMatrix:
case spv::Op::OpOuterProduct:
case spv::Op::OpDot:
case spv::Op::OpIAddCarry:
case spv::Op::OpISubBorrow:
case spv::Op::OpUMulExtended:
case spv::Op::OpSMulExtended:
case spv::Op::OpAny:
case spv::Op::OpAll:
case spv::Op::OpIsNan:
case spv::Op::OpIsInf:
case spv::Op::OpIsFinite:
case spv::Op::OpIsNormal:
case spv::Op::OpSignBitSet:
case spv::Op::OpLessOrGreater:
case spv::Op::OpOrdered:
case spv::Op::OpUnordered:
case spv::Op::OpLogicalEqual:
case spv::Op::OpLogicalNotEqual:
case spv::Op::OpLogicalOr:
case spv::Op::OpLogicalAnd:
case spv::Op::OpLogicalNot:
case spv::Op::OpSelect:
case spv::Op::OpIEqual:
case spv::Op::OpINotEqual:
case spv::Op::OpUGreaterThan:
case spv::Op::OpSGreaterThan:
case spv::Op::OpUGreaterThanEqual:
case spv::Op::OpSGreaterThanEqual:
case spv::Op::OpULessThan:
case spv::Op::OpSLessThan:
case spv::Op::OpULessThanEqual:
case spv::Op::OpSLessThanEqual:
case spv::Op::OpFOrdEqual:
case spv::Op::OpFUnordEqual:
case spv::Op::OpFOrdNotEqual:
case spv::Op::OpFUnordNotEqual:
case spv::Op::OpFOrdLessThan:
case spv::Op::OpFUnordLessThan:
case spv::Op::OpFOrdGreaterThan:
case spv::Op::OpFUnordGreaterThan:
case spv::Op::OpFOrdLessThanEqual:
case spv::Op::OpFUnordLessThanEqual:
case spv::Op::OpFOrdGreaterThanEqual:
case spv::Op::OpFUnordGreaterThanEqual:
case spv::Op::OpShiftRightLogical:
case spv::Op::OpShiftRightArithmetic:
case spv::Op::OpShiftLeftLogical:
case spv::Op::OpBitwiseOr:
case spv::Op::OpBitwiseXor:
case spv::Op::OpBitwiseAnd:
case spv::Op::OpNot:
case spv::Op::OpBitFieldInsert:
case spv::Op::OpBitFieldSExtract:
case spv::Op::OpBitFieldUExtract:
case spv::Op::OpBitReverse:
case spv::Op::OpBitCount:
case spv::Op::OpCopyLogical:
return true;
case spv::Op::OpExtInst: {
const auto* ext_inst_import =
ir_context->get_def_use_mgr()->GetDef(inst.GetSingleWordInOperand(0));
if (ext_inst_import->GetInOperand(0).AsString() != kExtensionSetName) {
return false;
}
switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
case GLSLstd450Round:
case GLSLstd450RoundEven:
case GLSLstd450Trunc:
case GLSLstd450FAbs:
case GLSLstd450SAbs:
case GLSLstd450FSign:
case GLSLstd450SSign:
case GLSLstd450Floor:
case GLSLstd450Ceil:
case GLSLstd450Fract:
case GLSLstd450Radians:
case GLSLstd450Degrees:
case GLSLstd450Sin:
case GLSLstd450Cos:
case GLSLstd450Tan:
case GLSLstd450Asin:
case GLSLstd450Acos:
case GLSLstd450Atan:
case GLSLstd450Sinh:
case GLSLstd450Cosh:
case GLSLstd450Tanh:
case GLSLstd450Asinh:
case GLSLstd450Acosh:
case GLSLstd450Atanh:
case GLSLstd450Atan2:
case GLSLstd450Pow:
case GLSLstd450Exp:
case GLSLstd450Log:
case GLSLstd450Exp2:
case GLSLstd450Log2:
case GLSLstd450Sqrt:
case GLSLstd450InverseSqrt:
case GLSLstd450Determinant:
case GLSLstd450MatrixInverse:
case GLSLstd450ModfStruct:
case GLSLstd450FMin:
case GLSLstd450UMin:
case GLSLstd450SMin:
case GLSLstd450FMax:
case GLSLstd450UMax:
case GLSLstd450SMax:
case GLSLstd450FClamp:
case GLSLstd450UClamp:
case GLSLstd450SClamp:
case GLSLstd450FMix:
case GLSLstd450IMix:
case GLSLstd450Step:
case GLSLstd450SmoothStep:
case GLSLstd450Fma:
case GLSLstd450FrexpStruct:
case GLSLstd450Ldexp:
case GLSLstd450PackSnorm4x8:
case GLSLstd450PackUnorm4x8:
case GLSLstd450PackSnorm2x16:
case GLSLstd450PackUnorm2x16:
case GLSLstd450PackHalf2x16:
case GLSLstd450PackDouble2x32:
case GLSLstd450UnpackSnorm2x16:
case GLSLstd450UnpackUnorm2x16:
case GLSLstd450UnpackHalf2x16:
case GLSLstd450UnpackSnorm4x8:
case GLSLstd450UnpackUnorm4x8:
case GLSLstd450UnpackDouble2x32:
case GLSLstd450Length:
case GLSLstd450Distance:
case GLSLstd450Cross:
case GLSLstd450Normalize:
case GLSLstd450FaceForward:
case GLSLstd450Reflect:
case GLSLstd450Refract:
case GLSLstd450FindILsb:
case GLSLstd450FindSMsb:
case GLSLstd450FindUMsb:
case GLSLstd450NMin:
case GLSLstd450NMax:
case GLSLstd450NClamp:
return true;
default:
return false;
}
}
default:
return false;
}
}
bool TransformationMoveInstructionDown::IsMemoryReadInstruction(
opt::IRContext* ir_context, const opt::Instruction& inst) {
switch (inst.opcode()) {
case spv::Op::OpLoad:
case spv::Op::OpCopyMemory:
case spv::Op::OpImageSampleImplicitLod:
case spv::Op::OpImageSampleExplicitLod:
case spv::Op::OpImageSampleDrefImplicitLod:
case spv::Op::OpImageSampleDrefExplicitLod:
case spv::Op::OpImageSampleProjImplicitLod:
case spv::Op::OpImageSampleProjExplicitLod:
case spv::Op::OpImageSampleProjDrefImplicitLod:
case spv::Op::OpImageSampleProjDrefExplicitLod:
case spv::Op::OpImageFetch:
case spv::Op::OpImageGather:
case spv::Op::OpImageDrefGather:
case spv::Op::OpImageRead:
case spv::Op::OpImageSparseSampleImplicitLod:
case spv::Op::OpImageSparseSampleExplicitLod:
case spv::Op::OpImageSparseSampleDrefImplicitLod:
case spv::Op::OpImageSparseSampleDrefExplicitLod:
case spv::Op::OpImageSparseSampleProjImplicitLod:
case spv::Op::OpImageSparseSampleProjExplicitLod:
case spv::Op::OpImageSparseSampleProjDrefImplicitLod:
case spv::Op::OpImageSparseSampleProjDrefExplicitLod:
case spv::Op::OpImageSparseFetch:
case spv::Op::OpImageSparseGather:
case spv::Op::OpImageSparseDrefGather:
case spv::Op::OpImageSparseRead:
case spv::Op::OpAtomicLoad:
case spv::Op::OpAtomicExchange:
case spv::Op::OpAtomicCompareExchange:
case spv::Op::OpAtomicCompareExchangeWeak:
case spv::Op::OpAtomicIIncrement:
case spv::Op::OpAtomicIDecrement:
case spv::Op::OpAtomicIAdd:
case spv::Op::OpAtomicISub:
case spv::Op::OpAtomicSMin:
case spv::Op::OpAtomicUMin:
case spv::Op::OpAtomicSMax:
case spv::Op::OpAtomicUMax:
case spv::Op::OpAtomicAnd:
case spv::Op::OpAtomicOr:
case spv::Op::OpAtomicXor:
case spv::Op::OpAtomicFlagTestAndSet:
return true;
case spv::Op::OpExtInst: {
if (GetExtensionSet(ir_context, inst) != kExtensionSetName) {
return false;
}
switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
case GLSLstd450InterpolateAtCentroid:
case GLSLstd450InterpolateAtOffset:
case GLSLstd450InterpolateAtSample:
return true;
default:
return false;
}
}
default:
return false;
}
}
uint32_t TransformationMoveInstructionDown::GetMemoryReadTarget(
opt::IRContext* ir_context, const opt::Instruction& inst) {
(void)ir_context;
assert(IsMemoryReadInstruction(ir_context, inst) &&
"|inst| is not a memory read instruction");
switch (inst.opcode()) {
case spv::Op::OpLoad:
case spv::Op::OpImageSampleImplicitLod:
case spv::Op::OpImageSampleExplicitLod:
case spv::Op::OpImageSampleDrefImplicitLod:
case spv::Op::OpImageSampleDrefExplicitLod:
case spv::Op::OpImageSampleProjImplicitLod:
case spv::Op::OpImageSampleProjExplicitLod:
case spv::Op::OpImageSampleProjDrefImplicitLod:
case spv::Op::OpImageSampleProjDrefExplicitLod:
case spv::Op::OpImageFetch:
case spv::Op::OpImageGather:
case spv::Op::OpImageDrefGather:
case spv::Op::OpImageRead:
case spv::Op::OpImageSparseSampleImplicitLod:
case spv::Op::OpImageSparseSampleExplicitLod:
case spv::Op::OpImageSparseSampleDrefImplicitLod:
case spv::Op::OpImageSparseSampleDrefExplicitLod:
case spv::Op::OpImageSparseSampleProjImplicitLod:
case spv::Op::OpImageSparseSampleProjExplicitLod:
case spv::Op::OpImageSparseSampleProjDrefImplicitLod:
case spv::Op::OpImageSparseSampleProjDrefExplicitLod:
case spv::Op::OpImageSparseFetch:
case spv::Op::OpImageSparseGather:
case spv::Op::OpImageSparseDrefGather:
case spv::Op::OpImageSparseRead:
case spv::Op::OpAtomicLoad:
case spv::Op::OpAtomicExchange:
case spv::Op::OpAtomicCompareExchange:
case spv::Op::OpAtomicCompareExchangeWeak:
case spv::Op::OpAtomicIIncrement:
case spv::Op::OpAtomicIDecrement:
case spv::Op::OpAtomicIAdd:
case spv::Op::OpAtomicISub:
case spv::Op::OpAtomicSMin:
case spv::Op::OpAtomicUMin:
case spv::Op::OpAtomicSMax:
case spv::Op::OpAtomicUMax:
case spv::Op::OpAtomicAnd:
case spv::Op::OpAtomicOr:
case spv::Op::OpAtomicXor:
case spv::Op::OpAtomicFlagTestAndSet:
return inst.GetSingleWordInOperand(0);
case spv::Op::OpCopyMemory:
return inst.GetSingleWordInOperand(1);
case spv::Op::OpExtInst: {
assert(GetExtensionSet(ir_context, inst) == kExtensionSetName &&
"Extension set is not supported");
switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
case GLSLstd450InterpolateAtCentroid:
case GLSLstd450InterpolateAtOffset:
case GLSLstd450InterpolateAtSample:
return inst.GetSingleWordInOperand(2);
default:
assert(false && "Not all memory opcodes are handled");
return 0;
}
}
default:
assert(false && "Not all memory opcodes are handled");
return 0;
}
}
bool TransformationMoveInstructionDown::IsMemoryWriteInstruction(
opt::IRContext* ir_context, const opt::Instruction& inst) {
switch (inst.opcode()) {
case spv::Op::OpStore:
case spv::Op::OpCopyMemory:
case spv::Op::OpImageWrite:
case spv::Op::OpAtomicStore:
case spv::Op::OpAtomicExchange:
case spv::Op::OpAtomicCompareExchange:
case spv::Op::OpAtomicCompareExchangeWeak:
case spv::Op::OpAtomicIIncrement:
case spv::Op::OpAtomicIDecrement:
case spv::Op::OpAtomicIAdd:
case spv::Op::OpAtomicISub:
case spv::Op::OpAtomicSMin:
case spv::Op::OpAtomicUMin:
case spv::Op::OpAtomicSMax:
case spv::Op::OpAtomicUMax:
case spv::Op::OpAtomicAnd:
case spv::Op::OpAtomicOr:
case spv::Op::OpAtomicXor:
case spv::Op::OpAtomicFlagTestAndSet:
case spv::Op::OpAtomicFlagClear:
return true;
case spv::Op::OpExtInst: {
if (GetExtensionSet(ir_context, inst) != kExtensionSetName) {
return false;
}
auto extension = static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1));
return extension == GLSLstd450Modf || extension == GLSLstd450Frexp;
}
default:
return false;
}
}
uint32_t TransformationMoveInstructionDown::GetMemoryWriteTarget(
opt::IRContext* ir_context, const opt::Instruction& inst) {
(void)ir_context;
assert(IsMemoryWriteInstruction(ir_context, inst) &&
"|inst| is not a memory write instruction");
switch (inst.opcode()) {
case spv::Op::OpStore:
case spv::Op::OpCopyMemory:
case spv::Op::OpImageWrite:
case spv::Op::OpAtomicStore:
case spv::Op::OpAtomicExchange:
case spv::Op::OpAtomicCompareExchange:
case spv::Op::OpAtomicCompareExchangeWeak:
case spv::Op::OpAtomicIIncrement:
case spv::Op::OpAtomicIDecrement:
case spv::Op::OpAtomicIAdd:
case spv::Op::OpAtomicISub:
case spv::Op::OpAtomicSMin:
case spv::Op::OpAtomicUMin:
case spv::Op::OpAtomicSMax:
case spv::Op::OpAtomicUMax:
case spv::Op::OpAtomicAnd:
case spv::Op::OpAtomicOr:
case spv::Op::OpAtomicXor:
case spv::Op::OpAtomicFlagTestAndSet:
case spv::Op::OpAtomicFlagClear:
return inst.GetSingleWordInOperand(0);
case spv::Op::OpExtInst: {
assert(GetExtensionSet(ir_context, inst) == kExtensionSetName &&
"Extension set is not supported");
switch (static_cast<GLSLstd450>(inst.GetSingleWordInOperand(1))) {
case GLSLstd450Modf:
case GLSLstd450Frexp:
return inst.GetSingleWordInOperand(3);
default:
assert(false && "Not all opcodes are handled");
return 0;
}
}
default:
assert(false && "Not all opcodes are handled");
return 0;
}
}
bool TransformationMoveInstructionDown::IsMemoryInstruction(
opt::IRContext* ir_context, const opt::Instruction& inst) {
return IsMemoryReadInstruction(ir_context, inst) ||
IsMemoryWriteInstruction(ir_context, inst);
}
bool TransformationMoveInstructionDown::IsBarrierInstruction(
const opt::Instruction& inst) {
switch (inst.opcode()) {
case spv::Op::OpMemoryBarrier:
case spv::Op::OpControlBarrier:
case spv::Op::OpMemoryNamedBarrier:
return true;
default:
return false;
}
}
bool TransformationMoveInstructionDown::CanSafelySwapInstructions(
opt::IRContext* ir_context, const opt::Instruction& a,
const opt::Instruction& b, const FactManager& fact_manager) {
assert(IsInstructionSupported(ir_context, a) &&
IsInstructionSupported(ir_context, b) &&
"Both opcodes must be supported");
if (IsSimpleInstruction(ir_context, a) ||
IsSimpleInstruction(ir_context, b)) {
return true;
}
if (IsBarrierInstruction(a) || IsBarrierInstruction(b)) {
return false;
}
if (!IsMemoryWriteInstruction(ir_context, a) &&
!IsMemoryWriteInstruction(ir_context, b)) {
return true;
}
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)))) {
return true;
}
if (IsMemoryReadInstruction(ir_context, a) &&
IsMemoryWriteInstruction(ir_context, b) &&
!memory_is_irrelevant(GetMemoryReadTarget(ir_context, a)) &&
!memory_is_irrelevant(GetMemoryWriteTarget(ir_context, b))) {
return false;
}
if (IsMemoryWriteInstruction(ir_context, a) &&
IsMemoryReadInstruction(ir_context, b) &&
!memory_is_irrelevant(GetMemoryWriteTarget(ir_context, a)) &&
!memory_is_irrelevant(GetMemoryReadTarget(ir_context, b))) {
return false;
}
return IsMemoryReadInstruction(ir_context, a) ||
IsMemoryReadInstruction(ir_context, b);
}
std::unordered_set<uint32_t> TransformationMoveInstructionDown::GetFreshIds()
const {
return std::unordered_set<uint32_t>();
}
}
}