From 0065c5672d9e13d211806d441212e71f32a7ccb2 Mon Sep 17 00:00:00 2001 From: Mostafa Ashraf Date: Tue, 3 Aug 2021 22:51:25 +0200 Subject: [PATCH] spirv-fuzz: support AtomicLoad (#4330) Enhances the TransformationLoad transformation and associated fuzzer pass to support atomic operations. Fixes #4324. --- source/fuzz/fuzzer_context.cpp | 3 + source/fuzz/fuzzer_context.h | 4 + source/fuzz/fuzzer_pass_add_loads.cpp | 67 +++- source/fuzz/protobufs/spvtoolsfuzz.proto | 19 +- source/fuzz/transformation_load.cpp | 177 +++++++++-- source/fuzz/transformation_load.h | 15 +- test/fuzz/transformation_load_test.cpp | 375 ++++++++++++++++++++--- 7 files changed, 586 insertions(+), 74 deletions(-) diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp index eff702af1..69ec68a0e 100644 --- a/source/fuzz/fuzzer_context.cpp +++ b/source/fuzz/fuzzer_context.cpp @@ -42,6 +42,7 @@ const std::pair kChanceOfAddingAnotherPassToPassLoop = {50, const std::pair kChanceOfAddingAnotherStructField = {20, 90}; const std::pair kChanceOfAddingArrayOrStructType = {20, 90}; +const std::pair KChanceOfAddingAtomicLoad = {30, 90}; const std::pair kChanceOfAddingBitInstructionSynonym = {5, 20}; const std::pair @@ -216,6 +217,8 @@ FuzzerContext::FuzzerContext(std::unique_ptr random_generator, ChooseBetweenMinAndMax(kChanceOfAddingAnotherStructField); chance_of_adding_array_or_struct_type_ = ChooseBetweenMinAndMax(kChanceOfAddingArrayOrStructType); + chance_of_adding_atomic_load_ = + ChooseBetweenMinAndMax(KChanceOfAddingAtomicLoad); chance_of_adding_bit_instruction_synonym_ = ChooseBetweenMinAndMax(kChanceOfAddingBitInstructionSynonym); chance_of_adding_both_branches_when_replacing_opselect_ = diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h index b3f8cc359..e403aa040 100644 --- a/source/fuzz/fuzzer_context.h +++ b/source/fuzz/fuzzer_context.h @@ -142,6 +142,9 @@ class FuzzerContext { uint32_t GetChanceOfAddingArrayOrStructType() const { return chance_of_adding_array_or_struct_type_; } + uint32_t GetChanceOfAddingAtomicLoad() const { + return chance_of_adding_atomic_load_; + } uint32_t GetChanceOfAddingBitInstructionSynonym() const { return chance_of_adding_bit_instruction_synonym_; } @@ -492,6 +495,7 @@ class FuzzerContext { uint32_t chance_of_adding_another_pass_to_pass_loop_; uint32_t chance_of_adding_another_struct_field_; uint32_t chance_of_adding_array_or_struct_type_; + uint32_t chance_of_adding_atomic_load_; uint32_t chance_of_adding_bit_instruction_synonym_; uint32_t chance_of_adding_both_branches_when_replacing_opselect_; uint32_t chance_of_adding_composite_extract_; diff --git a/source/fuzz/fuzzer_pass_add_loads.cpp b/source/fuzz/fuzzer_pass_add_loads.cpp index ae6d9fdfd..04dddbd6c 100644 --- a/source/fuzz/fuzzer_pass_add_loads.cpp +++ b/source/fuzz/fuzzer_pass_add_loads.cpp @@ -39,18 +39,22 @@ void FuzzerPassAddLoads::Apply() { "The opcode of the instruction we might insert before must be " "the same as the opcode in the descriptor for the instruction"); - // Check whether it is legitimate to insert a load before this - // instruction. - if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) { - return; - } - // Randomly decide whether to try inserting a load here. if (!GetFuzzerContext()->ChoosePercentage( GetFuzzerContext()->GetChanceOfAddingLoad())) { return; } + // Check whether it is legitimate to insert a load or atomic load before + // this instruction. + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, inst_it)) { + return; + } + if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpAtomicLoad, + inst_it)) { + return; + } + std::vector relevant_instructions = FindAvailableInstructions( function, block, inst_it, @@ -80,13 +84,52 @@ void FuzzerPassAddLoads::Apply() { return; } - // Choose a pointer at random, and create and apply a loading - // transformation based on it. - ApplyTransformation(TransformationLoad( - GetFuzzerContext()->GetFreshId(), + auto chosen_instruction = relevant_instructions[GetFuzzerContext()->RandomIndex( - relevant_instructions)] - ->result_id(), + relevant_instructions)]; + + bool is_atomic_load = false; + uint32_t memory_scope_id = 0; + uint32_t memory_semantics_id = 0; + + auto storage_class = GetIRContext() + ->get_def_use_mgr() + ->GetDef(chosen_instruction->type_id()) + ->GetSingleWordInOperand(0); + + switch (storage_class) { + case SpvStorageClassStorageBuffer: + case SpvStorageClassPhysicalStorageBuffer: + case SpvStorageClassWorkgroup: + case SpvStorageClassCrossWorkgroup: + case SpvStorageClassAtomicCounter: + case SpvStorageClassImage: + if (GetFuzzerContext()->ChoosePercentage( + GetFuzzerContext()->GetChanceOfAddingAtomicLoad())) { + is_atomic_load = true; + + memory_scope_id = FindOrCreateConstant( + {SpvScopeInvocation}, + FindOrCreateIntegerType(32, GetFuzzerContext()->ChooseEven()), + false); + + memory_semantics_id = FindOrCreateConstant( + {static_cast( + TransformationLoad::GetMemorySemanticsForStorageClass( + static_cast(storage_class)))}, + FindOrCreateIntegerType(32, GetFuzzerContext()->ChooseEven()), + false); + } + break; + + default: + break; + } + + // Create and apply the transformation. + ApplyTransformation(TransformationLoad( + GetFuzzerContext()->GetFreshId(), chosen_instruction->result_id(), + is_atomic_load, memory_scope_id, memory_semantics_id, instruction_descriptor)); }); } diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto index 5618c1407..50fb1fd96 100644 --- a/source/fuzz/protobufs/spvtoolsfuzz.proto +++ b/source/fuzz/protobufs/spvtoolsfuzz.proto @@ -1569,17 +1569,26 @@ message TransformationInvertComparisonOperator { message TransformationLoad { - // Transformation that adds an OpLoad instruction from a pointer into an id. + // Transformation that adds an OpLoad or OpAtomicLoad instruction from a pointer into an id. - // The result of the load instruction + // The result of the load instruction. uint32 fresh_id = 1; - // The pointer to be loaded from + // The pointer to be loaded from. uint32 pointer_id = 2; + // True if and only if the load should be atomic. + bool is_atomic = 3; + + // The memory scope for the atomic load. Ignored unless |is_atomic| is true. + uint32 memory_scope_id = 4; + + // The memory semantics for the atomic load. Ignored unless |is_atomic| is true. + uint32 memory_semantics_id = 5; + // A descriptor for an instruction in a block before which the new OpLoad - // instruction should be inserted - InstructionDescriptor instruction_to_insert_before = 3; + // instruction should be inserted. + InstructionDescriptor instruction_to_insert_before = 6; } diff --git a/source/fuzz/transformation_load.cpp b/source/fuzz/transformation_load.cpp index e22f8dd29..af90f1cbc 100644 --- a/source/fuzz/transformation_load.cpp +++ b/source/fuzz/transformation_load.cpp @@ -24,10 +24,15 @@ TransformationLoad::TransformationLoad(protobufs::TransformationLoad message) : message_(std::move(message)) {} TransformationLoad::TransformationLoad( - uint32_t fresh_id, uint32_t pointer_id, + uint32_t fresh_id, uint32_t pointer_id, bool is_atomic, + uint32_t memory_scope, uint32_t memory_semantics, const protobufs::InstructionDescriptor& instruction_to_insert_before) { message_.set_fresh_id(fresh_id); message_.set_pointer_id(pointer_id); + message_.set_is_atomic(is_atomic); + message_.set_memory_scope_id(memory_scope); + message_.set_memory_semantics_id(memory_semantics); + *message_.mutable_instruction_to_insert_before() = instruction_to_insert_before; } @@ -68,11 +73,97 @@ bool TransformationLoad::IsApplicable( if (!insert_before) { return false; } - // ... and it must be legitimate to insert a store before it. - if (!fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, insert_before)) { + // ... and it must be legitimate to insert a load before it. + if (!message_.is_atomic() && + !fuzzerutil::CanInsertOpcodeBeforeInstruction(SpvOpLoad, insert_before)) { return false; } + if (message_.is_atomic() && !fuzzerutil::CanInsertOpcodeBeforeInstruction( + SpvOpAtomicLoad, insert_before)) { + return false; + } + + if (message_.is_atomic()) { + // Check the exists of memory scope and memory semantics ids. + auto memory_scope_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.memory_scope_id()); + auto memory_semantics_instruction = + ir_context->get_def_use_mgr()->GetDef(message_.memory_semantics_id()); + + if (!memory_scope_instruction) { + return false; + } + if (!memory_semantics_instruction) { + return false; + } + // The memory scope and memory semantics instructions must have the + // 'OpConstant' opcode. + if (memory_scope_instruction->opcode() != SpvOpConstant) { + return false; + } + if (memory_semantics_instruction->opcode() != SpvOpConstant) { + return false; + } + // The memory scope and memory semantics need to be available before + // |insert_before|. + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, insert_before, message_.memory_scope_id())) { + return false; + } + if (!fuzzerutil::IdIsAvailableBeforeInstruction( + ir_context, insert_before, message_.memory_semantics_id())) { + return false; + } + // The memory scope and memory semantics instructions must have an Integer + // operand type with signedness does not matters. + if (ir_context->get_def_use_mgr() + ->GetDef(memory_scope_instruction->type_id()) + ->opcode() != SpvOpTypeInt) { + return false; + } + if (ir_context->get_def_use_mgr() + ->GetDef(memory_semantics_instruction->type_id()) + ->opcode() != SpvOpTypeInt) { + return false; + } + + // The size of the integer for memory scope and memory semantics + // instructions must be equal to 32 bits. + auto memory_scope_int_width = + ir_context->get_def_use_mgr() + ->GetDef(memory_scope_instruction->type_id()) + ->GetSingleWordInOperand(0); + auto memory_semantics_int_width = + ir_context->get_def_use_mgr() + ->GetDef(memory_semantics_instruction->type_id()) + ->GetSingleWordInOperand(0); + + if (memory_scope_int_width != 32) { + return false; + } + if (memory_semantics_int_width != 32) { + return false; + } + + // The memory scope constant value must be that of SpvScopeInvocation. + auto memory_scope_const_value = + memory_scope_instruction->GetSingleWordInOperand(0); + if (memory_scope_const_value != SpvScopeInvocation) { + return false; + } + + // The memory semantics constant value must match the storage class of the + // pointer being loaded from. + auto memory_semantics_const_value = static_cast( + memory_semantics_instruction->GetSingleWordInOperand(0)); + if (memory_semantics_const_value != + GetMemorySemanticsForStorageClass(static_cast( + pointer_type->GetSingleWordInOperand(0)))) { + return false; + } + } + // The pointer needs to be available at the insertion point. return fuzzerutil::IdIsAvailableBeforeInstruction(ir_context, insert_before, message_.pointer_id()); @@ -80,22 +171,70 @@ bool TransformationLoad::IsApplicable( void TransformationLoad::Apply(opt::IRContext* ir_context, TransformationContext* /*unused*/) const { - uint32_t result_type = fuzzerutil::GetPointeeTypeIdFromPointerType( - ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id())); - fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); - auto insert_before = - FindInstruction(message_.instruction_to_insert_before(), ir_context); - auto new_instruction = MakeUnique( - ir_context, SpvOpLoad, result_type, message_.fresh_id(), - opt::Instruction::OperandList( - {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}})); - auto new_instruction_ptr = new_instruction.get(); - insert_before->InsertBefore(std::move(new_instruction)); - // Inform the def-use manager about the new instruction and record its basic - // block. - ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr); - ir_context->set_instr_block(new_instruction_ptr, - ir_context->get_instr_block(insert_before)); + if (message_.is_atomic()) { + // OpAtomicLoad instruction. + uint32_t result_type = fuzzerutil::GetPointeeTypeIdFromPointerType( + ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id())); + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + auto insert_before = + FindInstruction(message_.instruction_to_insert_before(), ir_context); + auto new_instruction = MakeUnique( + ir_context, SpvOpAtomicLoad, result_type, message_.fresh_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}, + {SPV_OPERAND_TYPE_SCOPE_ID, {message_.memory_scope_id()}}, + {SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID, + {message_.memory_semantics_id()}}})); + auto new_instruction_ptr = new_instruction.get(); + insert_before->InsertBefore(std::move(new_instruction)); + // Inform the def-use manager about the new instruction and record its basic + // block. + ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr); + ir_context->set_instr_block(new_instruction_ptr, + ir_context->get_instr_block(insert_before)); + } else { + // OpLoad instruction. + uint32_t result_type = fuzzerutil::GetPointeeTypeIdFromPointerType( + ir_context, fuzzerutil::GetTypeId(ir_context, message_.pointer_id())); + fuzzerutil::UpdateModuleIdBound(ir_context, message_.fresh_id()); + auto insert_before = + FindInstruction(message_.instruction_to_insert_before(), ir_context); + auto new_instruction = MakeUnique( + ir_context, SpvOpLoad, result_type, message_.fresh_id(), + opt::Instruction::OperandList( + {{SPV_OPERAND_TYPE_ID, {message_.pointer_id()}}})); + auto new_instruction_ptr = new_instruction.get(); + insert_before->InsertBefore(std::move(new_instruction)); + // Inform the def-use manager about the new instruction and record its basic + // block. + ir_context->get_def_use_mgr()->AnalyzeInstDefUse(new_instruction_ptr); + ir_context->set_instr_block(new_instruction_ptr, + ir_context->get_instr_block(insert_before)); + } +} + +SpvMemorySemanticsMask TransformationLoad::GetMemorySemanticsForStorageClass( + SpvStorageClass storage_class) { + switch (storage_class) { + case SpvStorageClassWorkgroup: + return SpvMemorySemanticsWorkgroupMemoryMask; + + case SpvStorageClassStorageBuffer: + case SpvStorageClassPhysicalStorageBuffer: + return SpvMemorySemanticsUniformMemoryMask; + + case SpvStorageClassCrossWorkgroup: + return SpvMemorySemanticsCrossWorkgroupMemoryMask; + + case SpvStorageClassAtomicCounter: + return SpvMemorySemanticsAtomicCounterMemoryMask; + + case SpvStorageClassImage: + return SpvMemorySemanticsImageMemoryMask; + + default: + return SpvMemorySemanticsMaskNone; + } } protobufs::Transformation TransformationLoad::ToMessage() const { diff --git a/source/fuzz/transformation_load.h b/source/fuzz/transformation_load.h index d10b0073d..92a20ab9c 100644 --- a/source/fuzz/transformation_load.h +++ b/source/fuzz/transformation_load.h @@ -28,11 +28,20 @@ class TransformationLoad : public Transformation { explicit TransformationLoad(protobufs::TransformationLoad message); TransformationLoad( - uint32_t fresh_id, uint32_t pointer_id, + uint32_t fresh_id, uint32_t pointer_id, bool is_atomic, + uint32_t memory_scope, uint32_t memory_semantics, const protobufs::InstructionDescriptor& instruction_to_insert_before); // - |message_.fresh_id| must be fresh // - |message_.pointer_id| must be the id of a pointer + // - |message_.is_atomic| must be true if want to work with OpAtomicLoad + // - If |is_atomic| is true then |message_memory_scope_id| must be the id of + // an OpConstant 32 bit integer instruction with the value + // SpvScopeInvocation. + // - If |is_atomic| is true then |message_.memory_semantics_id| must be the id + // of an OpConstant 32 bit integer instruction with the values + // SpvMemorySemanticsWorkgroupMemoryMask or + // SpvMemorySemanticsUniformMemoryMask. // - The pointer must not be OpConstantNull or OpUndef // - |message_.instruction_to_insert_before| must identify an instruction // before which it is valid to insert an OpLoad, and where @@ -49,6 +58,10 @@ class TransformationLoad : public Transformation { void Apply(opt::IRContext* ir_context, TransformationContext* transformation_context) const override; + // Returns memory semantics mask for specific storage class. + static SpvMemorySemanticsMask GetMemorySemanticsForStorageClass( + SpvStorageClass storage_class); + std::unordered_set GetFreshIds() const override; protobufs::Transformation ToMessage() const override; diff --git a/test/fuzz/transformation_load_test.cpp b/test/fuzz/transformation_load_test.cpp index 3b6587f4b..370826eb8 100644 --- a/test/fuzz/transformation_load_test.cpp +++ b/test/fuzz/transformation_load_test.cpp @@ -129,61 +129,69 @@ TEST(TransformationLoadTest, BasicTest) { // Pointers that cannot be used: // 60 - null - // 61 - undefined // Bad: id is not fresh - ASSERT_FALSE(TransformationLoad( - 33, 33, MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) - .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE( + TransformationLoad(33, 33, false, 0, 0, + MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); // Bad: attempt to load from 11 from outside its function - ASSERT_FALSE(TransformationLoad( - 100, 11, MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) - .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE( + TransformationLoad(100, 11, false, 0, 0, + MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); // Bad: pointer is not available - ASSERT_FALSE(TransformationLoad( - 100, 33, MakeInstructionDescriptor(45, SpvOpCopyObject, 0)) - .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE( + TransformationLoad(100, 33, false, 0, 0, + MakeInstructionDescriptor(45, SpvOpCopyObject, 0)) + .IsApplicable(context.get(), transformation_context)); // Bad: attempt to insert before OpVariable - ASSERT_FALSE(TransformationLoad( - 100, 27, MakeInstructionDescriptor(27, SpvOpVariable, 0)) - .IsApplicable(context.get(), transformation_context)); + ASSERT_FALSE( + TransformationLoad(100, 27, false, 0, 0, + MakeInstructionDescriptor(27, SpvOpVariable, 0)) + .IsApplicable(context.get(), transformation_context)); // Bad: pointer id does not exist ASSERT_FALSE( - TransformationLoad(100, 1000, + TransformationLoad(100, 1000, false, 0, 0, MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) .IsApplicable(context.get(), transformation_context)); // Bad: pointer id exists but does not have a type - ASSERT_FALSE(TransformationLoad( - 100, 5, MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) - .IsApplicable(context.get(), transformation_context)); - - // Bad: pointer id exists and has a type, but is not a pointer - ASSERT_FALSE(TransformationLoad( - 100, 24, MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) - .IsApplicable(context.get(), transformation_context)); - - // Bad: attempt to load from null pointer - ASSERT_FALSE(TransformationLoad( - 100, 60, MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) - .IsApplicable(context.get(), transformation_context)); - - // Bad: %40 is not available at the program point ASSERT_FALSE( - TransformationLoad(100, 40, MakeInstructionDescriptor(37, SpvOpReturn, 0)) + TransformationLoad(100, 5, false, 0, 0, + MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) .IsApplicable(context.get(), transformation_context)); - // Bad: The described instruction does not exist - ASSERT_FALSE(TransformationLoad( - 100, 33, MakeInstructionDescriptor(1000, SpvOpReturn, 0)) + // Bad: pointer id exists and has a type, but is not a pointer + ASSERT_FALSE( + TransformationLoad(100, 24, false, 0, 0, + MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: attempt to load from null pointer + ASSERT_FALSE( + TransformationLoad(100, 60, false, 0, 0, + MakeInstructionDescriptor(38, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: %40 is not available at the program point + ASSERT_FALSE(TransformationLoad(100, 40, false, 0, 0, + MakeInstructionDescriptor(37, SpvOpReturn, 0)) .IsApplicable(context.get(), transformation_context)); + // Bad: The described instruction does not exist + ASSERT_FALSE( + TransformationLoad(100, 33, false, 0, 0, + MakeInstructionDescriptor(1000, SpvOpReturn, 0)) + .IsApplicable(context.get(), transformation_context)); + { TransformationLoad transformation( - 100, 33, MakeInstructionDescriptor(38, SpvOpAccessChain, 0)); + 100, 33, false, 0, 0, + MakeInstructionDescriptor(38, SpvOpAccessChain, 0)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation, context.get(), @@ -194,7 +202,8 @@ TEST(TransformationLoadTest, BasicTest) { { TransformationLoad transformation( - 101, 46, MakeInstructionDescriptor(16, SpvOpReturnValue, 0)); + 101, 46, false, 0, 0, + MakeInstructionDescriptor(16, SpvOpReturnValue, 0)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation, context.get(), @@ -205,7 +214,8 @@ TEST(TransformationLoadTest, BasicTest) { { TransformationLoad transformation( - 102, 16, MakeInstructionDescriptor(16, SpvOpReturnValue, 0)); + 102, 16, false, 0, 0, + MakeInstructionDescriptor(16, SpvOpReturnValue, 0)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation, context.get(), @@ -216,7 +226,8 @@ TEST(TransformationLoadTest, BasicTest) { { TransformationLoad transformation( - 103, 40, MakeInstructionDescriptor(43, SpvOpAccessChain, 0)); + 103, 40, false, 0, 0, + MakeInstructionDescriptor(43, SpvOpAccessChain, 0)); ASSERT_TRUE( transformation.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation, context.get(), @@ -289,6 +300,296 @@ TEST(TransformationLoadTest, BasicTest) { ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); } +TEST(TransformationLoadTest, AtomicLoadTestCase) { + const std::string shader = R"( + OpCapability Shader + OpCapability Int8 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeInt 8 1 + %9 = OpTypeInt 32 0 + %26 = OpTypeFloat 32 + %8 = OpTypeStruct %6 + %10 = OpTypePointer StorageBuffer %8 + %11 = OpVariable %10 StorageBuffer + %19 = OpConstant %26 0 + %18 = OpConstant %9 1 + %12 = OpConstant %6 0 + %13 = OpTypePointer StorageBuffer %6 + %15 = OpConstant %6 4 + %16 = OpConstant %6 7 + %17 = OpConstant %7 4 + %20 = OpConstant %9 64 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %14 = OpAccessChain %13 %11 %12 + %24 = OpAccessChain %13 %11 %12 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + spvtools::ValidatorOptions validator_options; + ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, + kConsoleMessageConsumer)); + TransformationContext transformation_context( + MakeUnique(context.get()), validator_options); + + // Bad: id is not fresh. + ASSERT_FALSE( + TransformationLoad(14, 14, true, 15, 20, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: id 100 of memory scope instruction does not exist. + ASSERT_FALSE( + TransformationLoad(21, 14, true, 100, 20, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + // Bad: id 100 of memory semantics instruction does not exist. + ASSERT_FALSE( + TransformationLoad(21, 14, true, 15, 100, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + // Bad: memory scope should be |OpConstant| opcode. + ASSERT_FALSE( + TransformationLoad(21, 14, true, 5, 20, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + // Bad: memory semantics should be |OpConstant| opcode. + ASSERT_FALSE( + TransformationLoad(21, 14, true, 15, 5, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: The memory scope instruction must have an Integer operand. + ASSERT_FALSE( + TransformationLoad(21, 14, true, 15, 19, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + // Bad: The memory memory semantics instruction must have an Integer operand. + ASSERT_FALSE( + TransformationLoad(21, 14, true, 19, 20, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: Integer size of the memory scope must be equal to 32 bits. + ASSERT_FALSE( + TransformationLoad(21, 14, true, 17, 20, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: Integer size of memory semantics must be equal to 32 bits. + ASSERT_FALSE( + TransformationLoad(21, 14, true, 15, 17, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: memory scope value must be 4 (SpvScopeInvocation). + ASSERT_FALSE( + TransformationLoad(21, 14, true, 16, 20, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: memory semantics value must be either: + // 64 (SpvMemorySemanticsUniformMemoryMask) + // 256 (SpvMemorySemanticsWorkgroupMemoryMask) + ASSERT_FALSE( + TransformationLoad(21, 14, true, 15, 16, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: The described instruction does not exist + ASSERT_FALSE( + TransformationLoad(21, 14, false, 15, 20, + MakeInstructionDescriptor(150, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Successful transformations. + { + TransformationLoad transformation( + 21, 14, true, 15, 20, + MakeInstructionDescriptor(24, SpvOpAccessChain, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + ApplyAndCheckFreshIds(transformation, context.get(), + &transformation_context); + ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( + context.get(), validator_options, kConsoleMessageConsumer)); + } + + const std::string after_transformation = R"( + OpCapability Shader + OpCapability Int8 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 320 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeInt 8 1 + %9 = OpTypeInt 32 0 + %26 = OpTypeFloat 32 + %8 = OpTypeStruct %6 + %10 = OpTypePointer StorageBuffer %8 + %11 = OpVariable %10 StorageBuffer + %19 = OpConstant %26 0 + %18 = OpConstant %9 1 + %12 = OpConstant %6 0 + %13 = OpTypePointer StorageBuffer %6 + %15 = OpConstant %6 4 + %16 = OpConstant %6 7 + %17 = OpConstant %7 4 + %20 = OpConstant %9 64 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %14 = OpAccessChain %13 %11 %12 + %21 = OpAtomicLoad %6 %14 %15 %20 + %24 = OpAccessChain %13 %11 %12 + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + +TEST(TransformationLoadTest, AtomicLoadTestCaseForWorkgroupMemory) { + std::string shader = R"( + OpCapability Shader + OpCapability Int8 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %26 = OpTypeFloat 32 + %27 = OpTypeInt 8 1 + %7 = OpTypeInt 32 0 ; 0 means unsigned + %8 = OpConstant %7 0 + %17 = OpConstant %27 4 + %19 = OpConstant %26 0 + %9 = OpTypePointer Function %6 + %13 = OpTypeStruct %6 + %12 = OpTypePointer Workgroup %13 + %11 = OpVariable %12 Workgroup + %14 = OpConstant %6 0 + %15 = OpTypePointer Function %6 + %51 = OpTypePointer Private %6 + %21 = OpConstant %6 4 + %23 = OpConstant %6 256 + %25 = OpTypePointer Function %7 + %50 = OpTypePointer Workgroup %6 + %34 = OpTypeBool + %35 = OpConstantFalse %34 + %53 = OpVariable %51 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %37 None + OpBranchConditional %35 %36 %37 + %36 = OpLabel + %38 = OpAccessChain %50 %11 %14 + %40 = OpAccessChain %50 %11 %14 + OpBranch %37 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto consumer = nullptr; + const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption); + spvtools::ValidatorOptions validator_options; + ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, + kConsoleMessageConsumer)); + TransformationContext transformation_context( + MakeUnique(context.get()), validator_options); + + // Bad: Can't insert OpAccessChain before the id 23 of memory scope. + ASSERT_FALSE( + TransformationLoad(60, 38, true, 21, 23, + MakeInstructionDescriptor(23, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Bad: Can't insert OpAccessChain before the id 23 of memory semantics. + ASSERT_FALSE( + TransformationLoad(60, 38, true, 21, 23, + MakeInstructionDescriptor(21, SpvOpAccessChain, 0)) + .IsApplicable(context.get(), transformation_context)); + + // Successful transformations. + { + TransformationLoad transformation( + 60, 38, true, 21, 23, + MakeInstructionDescriptor(40, SpvOpAccessChain, 0)); + ASSERT_TRUE( + transformation.IsApplicable(context.get(), transformation_context)); + ApplyAndCheckFreshIds(transformation, context.get(), + &transformation_context); + ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( + context.get(), validator_options, kConsoleMessageConsumer)); + } + + std::string after_transformation = R"( + OpCapability Shader + OpCapability Int8 + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %26 = OpTypeFloat 32 + %27 = OpTypeInt 8 1 + %7 = OpTypeInt 32 0 ; 0 means unsigned + %8 = OpConstant %7 0 + %17 = OpConstant %27 4 + %19 = OpConstant %26 0 + %9 = OpTypePointer Function %6 + %13 = OpTypeStruct %6 + %12 = OpTypePointer Workgroup %13 + %11 = OpVariable %12 Workgroup + %14 = OpConstant %6 0 + %15 = OpTypePointer Function %6 + %51 = OpTypePointer Private %6 + %21 = OpConstant %6 4 + %23 = OpConstant %6 256 + %25 = OpTypePointer Function %7 + %50 = OpTypePointer Workgroup %6 + %34 = OpTypeBool + %35 = OpConstantFalse %34 + %53 = OpVariable %51 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpSelectionMerge %37 None + OpBranchConditional %35 %36 %37 + %36 = OpLabel + %38 = OpAccessChain %50 %11 %14 + %60 = OpAtomicLoad %6 %38 %21 %23 + %40 = OpAccessChain %50 %11 %14 + OpBranch %37 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + ASSERT_TRUE(IsEqual(env, after_transformation, context.get())); +} + } // namespace } // namespace fuzz } // namespace spvtools