// Copyright (c) 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "source/fuzz/transformation_add_function.h" #include "gtest/gtest.h" #include "source/fuzz/fuzzer_util.h" #include "source/fuzz/instruction_message.h" #include "test/fuzz/fuzz_test_util.h" namespace spvtools { namespace fuzz { namespace { protobufs::AccessChainClampingInfo MakeAccessClampingInfo( uint32_t access_chain_id, const std::vector>& compare_and_select_ids) { protobufs::AccessChainClampingInfo result; result.set_access_chain_id(access_chain_id); for (auto& compare_and_select_id : compare_and_select_ids) { auto pair = result.add_compare_and_select_ids(); pair->set_first(compare_and_select_id.first); pair->set_second(compare_and_select_id.second); } return result; } std::vector GetInstructionsForFunction( spv_target_env env, const MessageConsumer& consumer, const std::string& donor, uint32_t function_id) { std::vector result; const auto donor_context = BuildModule(env, consumer, donor, kFuzzAssembleOption); spvtools::ValidatorOptions validator_options; assert(fuzzerutil::IsValidAndWellFormed( donor_context.get(), validator_options, kConsoleMessageConsumer) && "The given donor must be valid."); for (auto& function : *donor_context->module()) { if (function.result_id() == function_id) { function.ForEachInst([&result](opt::Instruction* inst) { opt::Instruction::OperandList input_operands; for (uint32_t i = 0; i < inst->NumInOperands(); i++) { input_operands.push_back(inst->GetInOperand(i)); } result.push_back(MakeInstructionMessage(inst->opcode(), inst->type_id(), inst->result_id(), input_operands)); }); break; } } assert(!result.empty() && "The required function should have been found."); return result; } // Returns true if and only if every pointer parameter and variable associated // with |function_id| in |context| is known by |transformation_context| to be // irrelevant, with the exception of |loop_limiter_id|, which must not be // irrelevant. (It can be 0 if no loop limiter is expected, and 0 should not be // deemed irrelevant). bool AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( opt::IRContext* context, const TransformationContext& transformation_context, uint32_t function_id, uint32_t loop_limiter_id) { // Look at all the functions until the function of interest is found. for (auto& function : *context->module()) { if (function.result_id() != function_id) { continue; } // Check that the parameters are all irrelevant. bool found_non_irrelevant_parameter = false; function.ForEachParam([context, &transformation_context, &found_non_irrelevant_parameter]( opt::Instruction* inst) { if (context->get_def_use_mgr()->GetDef(inst->type_id())->opcode() == spv::Op::OpTypePointer && !transformation_context.GetFactManager()->PointeeValueIsIrrelevant( inst->result_id())) { found_non_irrelevant_parameter = true; } }); if (found_non_irrelevant_parameter) { // A non-irrelevant parameter was found. return false; } // Look through the instructions in the function's first block. for (auto& inst : *function.begin()) { if (inst.opcode() != spv::Op::OpVariable) { // We have found a non-variable instruction; this means we have gotten // past all variables, so we are done. return true; } // The variable should be irrelevant if and only if it is not the loop // limiter. if ((inst.result_id() == loop_limiter_id) == transformation_context.GetFactManager()->PointeeValueIsIrrelevant( inst.result_id())) { return false; } } assert(false && "We should have processed all variables and returned by " "this point."); } assert(false && "We should have found the function of interest."); return true; } TEST(TransformationAddFunctionTest, BasicTest) { std::string shader = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFloat 32 %9 = OpTypePointer Function %8 %10 = OpTypeFunction %8 %7 %9 %18 = OpConstant %8 0 %20 = OpConstant %6 0 %28 = OpTypeBool %37 = OpConstant %6 1 %42 = OpTypePointer Private %8 %43 = OpVariable %42 Private %47 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); TransformationAddFunction transformation1(std::vector( {MakeInstructionMessage(spv::Op::OpFunction, 8, 13, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_ID, {10}}}), MakeInstructionMessage(spv::Op::OpFunctionParameter, 7, 11, {}), MakeInstructionMessage(spv::Op::OpFunctionParameter, 9, 12, {}), MakeInstructionMessage(spv::Op::OpLabel, 0, 14, {}), MakeInstructionMessage(spv::Op::OpVariable, 9, 17, {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}}), MakeInstructionMessage(spv::Op::OpVariable, 7, 19, {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {17}}, {SPV_OPERAND_TYPE_ID, {18}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {19}}, {SPV_OPERAND_TYPE_ID, {20}}}), MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {21}}}), MakeInstructionMessage(spv::Op::OpLabel, 0, 21, {}), MakeInstructionMessage(spv::Op::OpLoopMerge, 0, 0, {{SPV_OPERAND_TYPE_ID, {23}}, {SPV_OPERAND_TYPE_ID, {24}}, {SPV_OPERAND_TYPE_LOOP_CONTROL, {uint32_t(spv::LoopControlMask::MaskNone)}}}), MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {25}}}), MakeInstructionMessage(spv::Op::OpLabel, 0, 25, {}), MakeInstructionMessage(spv::Op::OpLoad, 6, 26, {{SPV_OPERAND_TYPE_ID, {19}}}), MakeInstructionMessage(spv::Op::OpLoad, 6, 27, {{SPV_OPERAND_TYPE_ID, {11}}}), MakeInstructionMessage( spv::Op::OpSLessThan, 28, 29, {{SPV_OPERAND_TYPE_ID, {26}}, {SPV_OPERAND_TYPE_ID, {27}}}), MakeInstructionMessage(spv::Op::OpBranchConditional, 0, 0, {{SPV_OPERAND_TYPE_ID, {29}}, {SPV_OPERAND_TYPE_ID, {22}}, {SPV_OPERAND_TYPE_ID, {23}}}), MakeInstructionMessage(spv::Op::OpLabel, 0, 22, {}), MakeInstructionMessage(spv::Op::OpLoad, 8, 30, {{SPV_OPERAND_TYPE_ID, {12}}}), MakeInstructionMessage(spv::Op::OpLoad, 6, 31, {{SPV_OPERAND_TYPE_ID, {19}}}), MakeInstructionMessage(spv::Op::OpConvertSToF, 8, 32, {{SPV_OPERAND_TYPE_ID, {31}}}), MakeInstructionMessage( spv::Op::OpFMul, 8, 33, {{SPV_OPERAND_TYPE_ID, {30}}, {SPV_OPERAND_TYPE_ID, {32}}}), MakeInstructionMessage(spv::Op::OpLoad, 8, 34, {{SPV_OPERAND_TYPE_ID, {17}}}), MakeInstructionMessage( spv::Op::OpFAdd, 8, 35, {{SPV_OPERAND_TYPE_ID, {34}}, {SPV_OPERAND_TYPE_ID, {33}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {17}}, {SPV_OPERAND_TYPE_ID, {35}}}), MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {24}}}), MakeInstructionMessage(spv::Op::OpLabel, 0, 24, {}), MakeInstructionMessage(spv::Op::OpLoad, 6, 36, {{SPV_OPERAND_TYPE_ID, {19}}}), MakeInstructionMessage( spv::Op::OpIAdd, 6, 38, {{SPV_OPERAND_TYPE_ID, {36}}, {SPV_OPERAND_TYPE_ID, {37}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {19}}, {SPV_OPERAND_TYPE_ID, {38}}}), MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {21}}}), MakeInstructionMessage(spv::Op::OpLabel, 0, 23, {}), MakeInstructionMessage(spv::Op::OpLoad, 8, 39, {{SPV_OPERAND_TYPE_ID, {17}}}), MakeInstructionMessage(spv::Op::OpReturnValue, 0, 0, {{SPV_OPERAND_TYPE_ID, {39}}}), MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})})); ASSERT_TRUE( transformation1.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation1, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation1 = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFloat 32 %9 = OpTypePointer Function %8 %10 = OpTypeFunction %8 %7 %9 %18 = OpConstant %8 0 %20 = OpConstant %6 0 %28 = OpTypeBool %37 = OpConstant %6 1 %42 = OpTypePointer Private %8 %43 = OpVariable %42 Private %47 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %13 = OpFunction %8 None %10 %11 = OpFunctionParameter %7 %12 = OpFunctionParameter %9 %14 = OpLabel %17 = OpVariable %9 Function %19 = OpVariable %7 Function OpStore %17 %18 OpStore %19 %20 OpBranch %21 %21 = OpLabel OpLoopMerge %23 %24 None OpBranch %25 %25 = OpLabel %26 = OpLoad %6 %19 %27 = OpLoad %6 %11 %29 = OpSLessThan %28 %26 %27 OpBranchConditional %29 %22 %23 %22 = OpLabel %30 = OpLoad %8 %12 %31 = OpLoad %6 %19 %32 = OpConvertSToF %8 %31 %33 = OpFMul %8 %30 %32 %34 = OpLoad %8 %17 %35 = OpFAdd %8 %34 %33 OpStore %17 %35 OpBranch %24 %24 = OpLabel %36 = OpLoad %6 %19 %38 = OpIAdd %6 %36 %37 OpStore %19 %38 OpBranch %21 %23 = OpLabel %39 = OpLoad %8 %17 OpReturnValue %39 OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation1, context.get())); ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(14)); ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(21)); ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(22)); ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(23)); ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(24)); ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(25)); TransformationAddFunction transformation2(std::vector( {MakeInstructionMessage(spv::Op::OpFunction, 2, 15, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_ID, {3}}}), MakeInstructionMessage(spv::Op::OpLabel, 0, 16, {}), MakeInstructionMessage(spv::Op::OpVariable, 7, 44, {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}}), MakeInstructionMessage(spv::Op::OpVariable, 9, 45, {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}}), MakeInstructionMessage(spv::Op::OpVariable, 7, 48, {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}}), MakeInstructionMessage(spv::Op::OpVariable, 9, 49, {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {44}}, {SPV_OPERAND_TYPE_ID, {20}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {45}}, {SPV_OPERAND_TYPE_ID, {18}}}), MakeInstructionMessage(spv::Op::OpFunctionCall, 8, 46, {{SPV_OPERAND_TYPE_ID, {13}}, {SPV_OPERAND_TYPE_ID, {44}}, {SPV_OPERAND_TYPE_ID, {45}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {48}}, {SPV_OPERAND_TYPE_ID, {37}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {49}}, {SPV_OPERAND_TYPE_ID, {47}}}), MakeInstructionMessage(spv::Op::OpFunctionCall, 8, 50, {{SPV_OPERAND_TYPE_ID, {13}}, {SPV_OPERAND_TYPE_ID, {48}}, {SPV_OPERAND_TYPE_ID, {49}}}), MakeInstructionMessage( spv::Op::OpFAdd, 8, 51, {{SPV_OPERAND_TYPE_ID, {46}}, {SPV_OPERAND_TYPE_ID, {50}}}), MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {43}}, {SPV_OPERAND_TYPE_ID, {51}}}), MakeInstructionMessage(spv::Op::OpReturn, 0, 0, {}), MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})})); ASSERT_TRUE( transformation2.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(transformation2, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string after_transformation2 = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFloat 32 %9 = OpTypePointer Function %8 %10 = OpTypeFunction %8 %7 %9 %18 = OpConstant %8 0 %20 = OpConstant %6 0 %28 = OpTypeBool %37 = OpConstant %6 1 %42 = OpTypePointer Private %8 %43 = OpVariable %42 Private %47 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %13 = OpFunction %8 None %10 %11 = OpFunctionParameter %7 %12 = OpFunctionParameter %9 %14 = OpLabel %17 = OpVariable %9 Function %19 = OpVariable %7 Function OpStore %17 %18 OpStore %19 %20 OpBranch %21 %21 = OpLabel OpLoopMerge %23 %24 None OpBranch %25 %25 = OpLabel %26 = OpLoad %6 %19 %27 = OpLoad %6 %11 %29 = OpSLessThan %28 %26 %27 OpBranchConditional %29 %22 %23 %22 = OpLabel %30 = OpLoad %8 %12 %31 = OpLoad %6 %19 %32 = OpConvertSToF %8 %31 %33 = OpFMul %8 %30 %32 %34 = OpLoad %8 %17 %35 = OpFAdd %8 %34 %33 OpStore %17 %35 OpBranch %24 %24 = OpLabel %36 = OpLoad %6 %19 %38 = OpIAdd %6 %36 %37 OpStore %19 %38 OpBranch %21 %23 = OpLabel %39 = OpLoad %8 %17 OpReturnValue %39 OpFunctionEnd %15 = OpFunction %2 None %3 %16 = OpLabel %44 = OpVariable %7 Function %45 = OpVariable %9 Function %48 = OpVariable %7 Function %49 = OpVariable %9 Function OpStore %44 %20 OpStore %45 %18 %46 = OpFunctionCall %8 %13 %44 %45 OpStore %48 %37 OpStore %49 %47 %50 = OpFunctionCall %8 %13 %48 %49 %51 = OpFAdd %8 %46 %50 OpStore %43 %51 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, after_transformation2, context.get())); ASSERT_TRUE(transformation_context.GetFactManager()->BlockIsDead(16)); } TEST(TransformationAddFunctionTest, InapplicableTransformations) { std::string shader = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFloat 32 %9 = OpTypePointer Function %8 %10 = OpTypeFunction %8 %7 %9 %18 = OpConstant %8 0 %20 = OpConstant %6 0 %28 = OpTypeBool %37 = OpConstant %6 1 %42 = OpTypePointer Private %8 %43 = OpVariable %42 Private %47 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %13 = OpFunction %8 None %10 %11 = OpFunctionParameter %7 %12 = OpFunctionParameter %9 %14 = OpLabel %17 = OpVariable %9 Function %19 = OpVariable %7 Function OpStore %17 %18 OpStore %19 %20 OpBranch %21 %21 = OpLabel OpLoopMerge %23 %24 None OpBranch %25 %25 = OpLabel %26 = OpLoad %6 %19 %27 = OpLoad %6 %11 %29 = OpSLessThan %28 %26 %27 OpBranchConditional %29 %22 %23 %22 = OpLabel %30 = OpLoad %8 %12 %31 = OpLoad %6 %19 %32 = OpConvertSToF %8 %31 %33 = OpFMul %8 %30 %32 %34 = OpLoad %8 %17 %35 = OpFAdd %8 %34 %33 OpStore %17 %35 OpBranch %24 %24 = OpLabel %36 = OpLoad %6 %19 %38 = OpIAdd %6 %36 %37 OpStore %19 %38 OpBranch %21 %23 = OpLabel %39 = OpLoad %8 %17 OpReturnValue %39 OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // No instructions ASSERT_FALSE( TransformationAddFunction(std::vector({})) .IsApplicable(context.get(), transformation_context)); // No function begin ASSERT_FALSE( TransformationAddFunction( std::vector( {MakeInstructionMessage(spv::Op::OpFunctionParameter, 7, 11, {}), MakeInstructionMessage(spv::Op::OpFunctionParameter, 9, 12, {}), MakeInstructionMessage(spv::Op::OpLabel, 0, 14, {})})) .IsApplicable(context.get(), transformation_context)); // No OpLabel ASSERT_FALSE( TransformationAddFunction( std::vector( {MakeInstructionMessage( spv::Op::OpFunction, 8, 13, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_ID, {10}}}), MakeInstructionMessage(spv::Op::OpReturnValue, 0, 0, {{SPV_OPERAND_TYPE_ID, {39}}}), MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})})) .IsApplicable(context.get(), transformation_context)); // Abrupt end of instructions ASSERT_FALSE(TransformationAddFunction( std::vector({MakeInstructionMessage( spv::Op::OpFunction, 8, 13, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_ID, {10}}})})) .IsApplicable(context.get(), transformation_context)); // No function end ASSERT_FALSE(TransformationAddFunction( std::vector( {MakeInstructionMessage( spv::Op::OpFunction, 8, 13, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_ID, {10}}}), MakeInstructionMessage(spv::Op::OpLabel, 0, 14, {}), MakeInstructionMessage(spv::Op::OpReturnValue, 0, 0, {{SPV_OPERAND_TYPE_ID, {39}}})})) .IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddFunctionTest, LoopLimiters) { std::string shader = R"( OpCapability Shader %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 0 %7 = OpTypePointer Function %6 %8 = OpConstant %6 0 %9 = OpConstant %6 1 %10 = OpConstant %6 5 %11 = OpTypeBool %12 = OpConstantTrue %11 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; std::vector instructions; instructions.push_back( MakeInstructionMessage(spv::Op::OpFunction, 2, 30, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_TYPE_ID, {3}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 31, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {20}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 20, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpLoopMerge, 0, 0, {{SPV_OPERAND_TYPE_ID, {21}}, {SPV_OPERAND_TYPE_ID, {22}}, {SPV_OPERAND_TYPE_LOOP_CONTROL, {uint32_t(spv::LoopControlMask::MaskNone)}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranchConditional, 0, 0, {{SPV_OPERAND_TYPE_ID, {12}}, {SPV_OPERAND_TYPE_ID, {23}}, {SPV_OPERAND_TYPE_ID, {21}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 23, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpLoopMerge, 0, 0, {{SPV_OPERAND_TYPE_ID, {25}}, {SPV_OPERAND_TYPE_ID, {26}}, {SPV_OPERAND_TYPE_LOOP_CONTROL, {uint32_t(spv::LoopControlMask::MaskNone)}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {28}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 28, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranchConditional, 0, 0, {{SPV_OPERAND_TYPE_ID, {12}}, {SPV_OPERAND_TYPE_ID, {26}}, {SPV_OPERAND_TYPE_ID, {25}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 26, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {23}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 25, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpLoopMerge, 0, 0, {{SPV_OPERAND_TYPE_ID, {24}}, {SPV_OPERAND_TYPE_ID, {27}}, {SPV_OPERAND_TYPE_LOOP_CONTROL, {uint32_t(spv::LoopControlMask::MaskNone)}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranchConditional, 0, 0, {{SPV_OPERAND_TYPE_ID, {12}}, {SPV_OPERAND_TYPE_ID, {24}}, {SPV_OPERAND_TYPE_ID, {27}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 27, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {25}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 24, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {22}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 22, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranch, 0, 0, {{SPV_OPERAND_TYPE_ID, {20}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 21, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpReturn, 0, 0, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})); spvtools::ValidatorOptions validator_options; const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context1( MakeUnique(context1.get()), validator_options); TransformationContext transformation_context2( MakeUnique(context2.get()), validator_options); TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); ApplyAndCheckFreshIds(add_dead_function, context1.get(), &transformation_context1); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); // The added function should not be deemed livesafe. ASSERT_FALSE( transformation_context1.GetFactManager()->FunctionIsLivesafe(30)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context1.get(), transformation_context1, 30, 0)); std::string added_as_dead_code = R"( OpCapability Shader %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 0 %7 = OpTypePointer Function %6 %8 = OpConstant %6 0 %9 = OpConstant %6 1 %10 = OpConstant %6 5 %11 = OpTypeBool %12 = OpConstantTrue %11 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %30 = OpFunction %2 None %3 %31 = OpLabel OpBranch %20 %20 = OpLabel OpLoopMerge %21 %22 None OpBranchConditional %12 %23 %21 %23 = OpLabel OpLoopMerge %25 %26 None OpBranch %28 %28 = OpLabel OpBranchConditional %12 %26 %25 %26 = OpLabel OpBranch %23 %25 = OpLabel OpLoopMerge %24 %27 None OpBranchConditional %12 %24 %27 %27 = OpLabel OpBranch %25 %24 = OpLabel OpBranch %22 %22 = OpLabel OpBranch %20 %21 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get())); protobufs::LoopLimiterInfo loop_limiter1; loop_limiter1.set_loop_header_id(20); loop_limiter1.set_load_id(101); loop_limiter1.set_increment_id(102); loop_limiter1.set_compare_id(103); loop_limiter1.set_logical_op_id(104); protobufs::LoopLimiterInfo loop_limiter2; loop_limiter2.set_loop_header_id(23); loop_limiter2.set_load_id(105); loop_limiter2.set_increment_id(106); loop_limiter2.set_compare_id(107); loop_limiter2.set_logical_op_id(108); protobufs::LoopLimiterInfo loop_limiter3; loop_limiter3.set_loop_header_id(25); loop_limiter3.set_load_id(109); loop_limiter3.set_increment_id(110); loop_limiter3.set_compare_id(111); loop_limiter3.set_logical_op_id(112); std::vector loop_limiters = { loop_limiter1, loop_limiter2, loop_limiter3}; TransformationAddFunction add_livesafe_function(instructions, 100, 10, loop_limiters, 0, {}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(), transformation_context2)); ApplyAndCheckFreshIds(add_livesafe_function, context2.get(), &transformation_context2); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context2.get(), validator_options, kConsoleMessageConsumer)); // The added function should indeed be deemed livesafe. ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(30)); // All variables/parameters in the function should be deemed irrelevant, // except the loop limiter. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context2.get(), transformation_context2, 30, 100)); std::string added_as_livesafe_code = R"( OpCapability Shader %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 0 %7 = OpTypePointer Function %6 %8 = OpConstant %6 0 %9 = OpConstant %6 1 %10 = OpConstant %6 5 %11 = OpTypeBool %12 = OpConstantTrue %11 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %30 = OpFunction %2 None %3 %31 = OpLabel %100 = OpVariable %7 Function %8 OpBranch %20 %20 = OpLabel OpLoopMerge %21 %22 None OpBranchConditional %12 %23 %21 %23 = OpLabel OpLoopMerge %25 %26 None OpBranch %28 %28 = OpLabel OpBranchConditional %12 %26 %25 %26 = OpLabel %105 = OpLoad %6 %100 %106 = OpIAdd %6 %105 %9 OpStore %100 %106 %107 = OpUGreaterThanEqual %11 %105 %10 OpBranchConditional %107 %25 %23 %25 = OpLabel OpLoopMerge %24 %27 None OpBranchConditional %12 %24 %27 %27 = OpLabel %109 = OpLoad %6 %100 %110 = OpIAdd %6 %109 %9 OpStore %100 %110 %111 = OpUGreaterThanEqual %11 %109 %10 OpBranchConditional %111 %24 %25 %24 = OpLabel OpBranch %22 %22 = OpLabel %101 = OpLoad %6 %100 %102 = OpIAdd %6 %101 %9 OpStore %100 %102 %103 = OpUGreaterThanEqual %11 %101 %10 OpBranchConditional %103 %21 %20 %21 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_livesafe_code, context2.get())); } TEST(TransformationAddFunctionTest, KillAndUnreachableInVoidFunction) { std::string shader = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFunction %2 %7 %13 = OpConstant %6 2 %14 = OpTypeBool %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; std::vector instructions; instructions.push_back( MakeInstructionMessage(spv::Op::OpFunction, 2, 10, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_TYPE_ID, {8}}})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionParameter, 7, 9, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 11, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 12, {{SPV_OPERAND_TYPE_ID, {9}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpIEqual, 14, 15, {{SPV_OPERAND_TYPE_ID, {12}}, {SPV_OPERAND_TYPE_ID, {13}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpSelectionMerge, 0, 0, {{SPV_OPERAND_TYPE_ID, {17}}, {SPV_OPERAND_TYPE_SELECTION_CONTROL, {uint32_t(spv::SelectionControlMask::MaskNone)}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranchConditional, 0, 0, {{SPV_OPERAND_TYPE_ID, {15}}, {SPV_OPERAND_TYPE_ID, {16}}, {SPV_OPERAND_TYPE_ID, {17}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 16, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpUnreachable, 0, 0, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 17, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpKill, 0, 0, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})); spvtools::ValidatorOptions validator_options; const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context1( MakeUnique(context1.get()), validator_options); TransformationContext transformation_context2( MakeUnique(context2.get()), validator_options); TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); ApplyAndCheckFreshIds(add_dead_function, context1.get(), &transformation_context1); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); // The added function should not be deemed livesafe. ASSERT_FALSE( transformation_context1.GetFactManager()->FunctionIsLivesafe(10)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context1.get(), transformation_context1, 10, 0)); std::string added_as_dead_code = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFunction %2 %7 %13 = OpConstant %6 2 %14 = OpTypeBool %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %10 = OpFunction %2 None %8 %9 = OpFunctionParameter %7 %11 = OpLabel %12 = OpLoad %6 %9 %15 = OpIEqual %14 %12 %13 OpSelectionMerge %17 None OpBranchConditional %15 %16 %17 %16 = OpLabel OpUnreachable %17 = OpLabel OpKill OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get())); TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 0, {}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(), transformation_context2)); ApplyAndCheckFreshIds(add_livesafe_function, context2.get(), &transformation_context2); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context2.get(), validator_options, kConsoleMessageConsumer)); // The added function should indeed be deemed livesafe. ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(10)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context2.get(), transformation_context2, 10, 0)); std::string added_as_livesafe_code = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFunction %2 %7 %13 = OpConstant %6 2 %14 = OpTypeBool %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %10 = OpFunction %2 None %8 %9 = OpFunctionParameter %7 %11 = OpLabel %12 = OpLoad %6 %9 %15 = OpIEqual %14 %12 %13 OpSelectionMerge %17 None OpBranchConditional %15 %16 %17 %16 = OpLabel OpReturn %17 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_livesafe_code, context2.get())); } TEST(TransformationAddFunctionTest, KillAndUnreachableInNonVoidFunction) { std::string shader = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFunction %2 %7 %50 = OpTypeFunction %6 %7 %13 = OpConstant %6 2 %14 = OpTypeBool %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; std::vector instructions; instructions.push_back( MakeInstructionMessage(spv::Op::OpFunction, 6, 10, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_TYPE_ID, {50}}})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionParameter, 7, 9, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 11, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 12, {{SPV_OPERAND_TYPE_ID, {9}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpIEqual, 14, 15, {{SPV_OPERAND_TYPE_ID, {12}}, {SPV_OPERAND_TYPE_ID, {13}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpSelectionMerge, 0, 0, {{SPV_OPERAND_TYPE_ID, {17}}, {SPV_OPERAND_TYPE_SELECTION_CONTROL, {uint32_t(spv::SelectionControlMask::MaskNone)}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpBranchConditional, 0, 0, {{SPV_OPERAND_TYPE_ID, {15}}, {SPV_OPERAND_TYPE_ID, {16}}, {SPV_OPERAND_TYPE_ID, {17}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 16, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpUnreachable, 0, 0, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 17, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpKill, 0, 0, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})); spvtools::ValidatorOptions validator_options; const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context1( MakeUnique(context1.get()), validator_options); TransformationContext transformation_context2( MakeUnique(context2.get()), validator_options); TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); ApplyAndCheckFreshIds(add_dead_function, context1.get(), &transformation_context1); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); // The added function should not be deemed livesafe. ASSERT_FALSE( transformation_context1.GetFactManager()->FunctionIsLivesafe(10)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context1.get(), transformation_context1, 10, 0)); std::string added_as_dead_code = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFunction %2 %7 %50 = OpTypeFunction %6 %7 %13 = OpConstant %6 2 %14 = OpTypeBool %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %10 = OpFunction %6 None %50 %9 = OpFunctionParameter %7 %11 = OpLabel %12 = OpLoad %6 %9 %15 = OpIEqual %14 %12 %13 OpSelectionMerge %17 None OpBranchConditional %15 %16 %17 %16 = OpLabel OpUnreachable %17 = OpLabel OpKill OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get())); TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 13, {}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(), transformation_context2)); ApplyAndCheckFreshIds(add_livesafe_function, context2.get(), &transformation_context2); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context2.get(), validator_options, kConsoleMessageConsumer)); // The added function should indeed be deemed livesafe. ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(10)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context2.get(), transformation_context2, 10, 0)); std::string added_as_livesafe_code = R"( OpCapability Shader %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 %7 = OpTypePointer Function %6 %8 = OpTypeFunction %2 %7 %50 = OpTypeFunction %6 %7 %13 = OpConstant %6 2 %14 = OpTypeBool %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %10 = OpFunction %6 None %50 %9 = OpFunctionParameter %7 %11 = OpLabel %12 = OpLoad %6 %9 %15 = OpIEqual %14 %12 %13 OpSelectionMerge %17 None OpBranchConditional %15 %16 %17 %16 = OpLabel OpReturnValue %13 %17 = OpLabel OpReturnValue %13 OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_livesafe_code, context2.get())); } TEST(TransformationAddFunctionTest, ClampedAccessChains) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %100 = OpTypeBool %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %15 = OpTypeInt 32 0 %102 = OpTypePointer Function %15 %8 = OpTypeFunction %2 %7 %102 %7 %16 = OpConstant %15 5 %17 = OpTypeArray %6 %16 %18 = OpTypeArray %17 %16 %19 = OpTypePointer Private %18 %20 = OpVariable %19 Private %21 = OpConstant %6 0 %23 = OpTypePointer Private %6 %26 = OpTypePointer Function %17 %29 = OpTypePointer Private %17 %33 = OpConstant %6 4 %200 = OpConstant %15 4 %35 = OpConstant %15 10 %36 = OpTypeArray %6 %35 %37 = OpTypePointer Private %36 %38 = OpVariable %37 Private %54 = OpTypeFloat 32 %55 = OpTypeVector %54 4 %56 = OpTypePointer Private %55 %57 = OpVariable %56 Private %59 = OpTypeVector %54 3 %60 = OpTypeMatrix %59 2 %61 = OpTypePointer Private %60 %62 = OpVariable %61 Private %64 = OpTypePointer Private %54 %69 = OpConstant %54 2 %71 = OpConstant %6 1 %72 = OpConstant %6 2 %201 = OpConstant %15 2 %73 = OpConstant %6 3 %202 = OpConstant %15 3 %203 = OpConstant %6 1 %204 = OpConstant %6 9 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; std::vector instructions; instructions.push_back( MakeInstructionMessage(spv::Op::OpFunction, 2, 12, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_TYPE_ID, {8}}})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionParameter, 7, 9, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionParameter, 102, 10, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionParameter, 7, 11, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 13, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpVariable, 7, 14, {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}})); instructions.push_back( MakeInstructionMessage(spv::Op::OpVariable, 26, 27, {{SPV_OPERAND_TYPE_STORAGE_CLASS, {uint32_t(spv::StorageClass::Function)}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 22, {{SPV_OPERAND_TYPE_ID, {11}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpAccessChain, 23, 24, {{SPV_OPERAND_TYPE_ID, {20}}, {SPV_OPERAND_TYPE_ID, {21}}, {SPV_OPERAND_TYPE_ID, {22}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 25, {{SPV_OPERAND_TYPE_ID, {24}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {14}}, {SPV_OPERAND_TYPE_ID, {25}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 15, 28, {{SPV_OPERAND_TYPE_ID, {10}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpAccessChain, 29, 30, {{SPV_OPERAND_TYPE_ID, {20}}, {SPV_OPERAND_TYPE_ID, {28}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 17, 31, {{SPV_OPERAND_TYPE_ID, {30}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {27}}, {SPV_OPERAND_TYPE_ID, {31}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 32, {{SPV_OPERAND_TYPE_ID, {9}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpInBoundsAccessChain, 7, 34, {{SPV_OPERAND_TYPE_ID, {27}}, {SPV_OPERAND_TYPE_ID, {32}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {34}}, {SPV_OPERAND_TYPE_ID, {33}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 39, {{SPV_OPERAND_TYPE_ID, {9}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpAccessChain, 23, 40, {{SPV_OPERAND_TYPE_ID, {38}}, {SPV_OPERAND_TYPE_ID, {33}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 41, {{SPV_OPERAND_TYPE_ID, {40}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpInBoundsAccessChain, 23, 42, {{SPV_OPERAND_TYPE_ID, {38}}, {SPV_OPERAND_TYPE_ID, {39}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {42}}, {SPV_OPERAND_TYPE_ID, {41}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 15, 43, {{SPV_OPERAND_TYPE_ID, {10}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 44, {{SPV_OPERAND_TYPE_ID, {11}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 45, {{SPV_OPERAND_TYPE_ID, {9}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 15, 46, {{SPV_OPERAND_TYPE_ID, {10}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpIAdd, 6, 47, {{SPV_OPERAND_TYPE_ID, {45}}, {SPV_OPERAND_TYPE_ID, {46}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpAccessChain, 23, 48, {{SPV_OPERAND_TYPE_ID, {38}}, {SPV_OPERAND_TYPE_ID, {47}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 49, {{SPV_OPERAND_TYPE_ID, {48}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpInBoundsAccessChain, 23, 50, {{SPV_OPERAND_TYPE_ID, {20}}, {SPV_OPERAND_TYPE_ID, {43}}, {SPV_OPERAND_TYPE_ID, {44}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 51, {{SPV_OPERAND_TYPE_ID, {50}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpIAdd, 6, 52, {{SPV_OPERAND_TYPE_ID, {51}}, {SPV_OPERAND_TYPE_ID, {49}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpAccessChain, 23, 53, {{SPV_OPERAND_TYPE_ID, {20}}, {SPV_OPERAND_TYPE_ID, {43}}, {SPV_OPERAND_TYPE_ID, {44}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {53}}, {SPV_OPERAND_TYPE_ID, {52}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 15, 58, {{SPV_OPERAND_TYPE_ID, {10}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 63, {{SPV_OPERAND_TYPE_ID, {11}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpAccessChain, 64, 65, {{SPV_OPERAND_TYPE_ID, {62}}, {SPV_OPERAND_TYPE_ID, {21}}, {SPV_OPERAND_TYPE_ID, {63}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpAccessChain, 64, 101, {{SPV_OPERAND_TYPE_ID, {62}}, {SPV_OPERAND_TYPE_ID, {45}}, {SPV_OPERAND_TYPE_ID, {46}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 54, 66, {{SPV_OPERAND_TYPE_ID, {65}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpAccessChain, 64, 67, {{SPV_OPERAND_TYPE_ID, {57}}, {SPV_OPERAND_TYPE_ID, {58}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {67}}, {SPV_OPERAND_TYPE_ID, {66}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLoad, 6, 68, {{SPV_OPERAND_TYPE_ID, {9}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpInBoundsAccessChain, 64, 70, {{SPV_OPERAND_TYPE_ID, {57}}, {SPV_OPERAND_TYPE_ID, {68}}})); instructions.push_back(MakeInstructionMessage( spv::Op::OpStore, 0, 0, {{SPV_OPERAND_TYPE_ID, {70}}, {SPV_OPERAND_TYPE_ID, {69}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpReturn, 0, 0, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})); spvtools::ValidatorOptions validator_options; const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context1( MakeUnique(context1.get()), validator_options); TransformationContext transformation_context2( MakeUnique(context2.get()), validator_options); TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); ApplyAndCheckFreshIds(add_dead_function, context1.get(), &transformation_context1); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); // The function should not be deemed livesafe ASSERT_FALSE( transformation_context1.GetFactManager()->FunctionIsLivesafe(12)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context1.get(), transformation_context1, 12, 0)); std::string added_as_dead_code = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %100 = OpTypeBool %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %15 = OpTypeInt 32 0 %102 = OpTypePointer Function %15 %8 = OpTypeFunction %2 %7 %102 %7 %16 = OpConstant %15 5 %17 = OpTypeArray %6 %16 %18 = OpTypeArray %17 %16 %19 = OpTypePointer Private %18 %20 = OpVariable %19 Private %21 = OpConstant %6 0 %23 = OpTypePointer Private %6 %26 = OpTypePointer Function %17 %29 = OpTypePointer Private %17 %33 = OpConstant %6 4 %200 = OpConstant %15 4 %35 = OpConstant %15 10 %36 = OpTypeArray %6 %35 %37 = OpTypePointer Private %36 %38 = OpVariable %37 Private %54 = OpTypeFloat 32 %55 = OpTypeVector %54 4 %56 = OpTypePointer Private %55 %57 = OpVariable %56 Private %59 = OpTypeVector %54 3 %60 = OpTypeMatrix %59 2 %61 = OpTypePointer Private %60 %62 = OpVariable %61 Private %64 = OpTypePointer Private %54 %69 = OpConstant %54 2 %71 = OpConstant %6 1 %72 = OpConstant %6 2 %201 = OpConstant %15 2 %73 = OpConstant %6 3 %202 = OpConstant %15 3 %203 = OpConstant %6 1 %204 = OpConstant %6 9 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %12 = OpFunction %2 None %8 %9 = OpFunctionParameter %7 %10 = OpFunctionParameter %102 %11 = OpFunctionParameter %7 %13 = OpLabel %14 = OpVariable %7 Function %27 = OpVariable %26 Function %22 = OpLoad %6 %11 %24 = OpAccessChain %23 %20 %21 %22 %25 = OpLoad %6 %24 OpStore %14 %25 %28 = OpLoad %15 %10 %30 = OpAccessChain %29 %20 %28 %31 = OpLoad %17 %30 OpStore %27 %31 %32 = OpLoad %6 %9 %34 = OpInBoundsAccessChain %7 %27 %32 OpStore %34 %33 %39 = OpLoad %6 %9 %40 = OpAccessChain %23 %38 %33 %41 = OpLoad %6 %40 %42 = OpInBoundsAccessChain %23 %38 %39 OpStore %42 %41 %43 = OpLoad %15 %10 %44 = OpLoad %6 %11 %45 = OpLoad %6 %9 %46 = OpLoad %15 %10 %47 = OpIAdd %6 %45 %46 %48 = OpAccessChain %23 %38 %47 %49 = OpLoad %6 %48 %50 = OpInBoundsAccessChain %23 %20 %43 %44 %51 = OpLoad %6 %50 %52 = OpIAdd %6 %51 %49 %53 = OpAccessChain %23 %20 %43 %44 OpStore %53 %52 %58 = OpLoad %15 %10 %63 = OpLoad %6 %11 %65 = OpAccessChain %64 %62 %21 %63 %101 = OpAccessChain %64 %62 %45 %46 %66 = OpLoad %54 %65 %67 = OpAccessChain %64 %57 %58 OpStore %67 %66 %68 = OpLoad %6 %9 %70 = OpInBoundsAccessChain %64 %57 %68 OpStore %70 %69 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get())); std::vector access_chain_clamping_info; access_chain_clamping_info.push_back( MakeAccessClampingInfo(24, {{1001, 2001}, {1002, 2002}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(30, {{1003, 2003}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(34, {{1004, 2004}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(40, {{1005, 2005}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(42, {{1006, 2006}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(48, {{1007, 2007}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(50, {{1008, 2008}, {1009, 2009}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(53, {{1010, 2010}, {1011, 2011}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(65, {{1012, 2012}, {1013, 2013}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(101, {{1014, 2014}, {1015, 2015}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(67, {{1016, 2016}})); access_chain_clamping_info.push_back( MakeAccessClampingInfo(70, {{1017, 2017}})); TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 13, access_chain_clamping_info); ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(), transformation_context2)); ApplyAndCheckFreshIds(add_livesafe_function, context2.get(), &transformation_context2); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context2.get(), validator_options, kConsoleMessageConsumer)); // The function should be deemed livesafe ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(12)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context2.get(), transformation_context2, 12, 0)); std::string added_as_livesafe_code = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %100 = OpTypeBool %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %15 = OpTypeInt 32 0 %102 = OpTypePointer Function %15 %8 = OpTypeFunction %2 %7 %102 %7 %16 = OpConstant %15 5 %17 = OpTypeArray %6 %16 %18 = OpTypeArray %17 %16 %19 = OpTypePointer Private %18 %20 = OpVariable %19 Private %21 = OpConstant %6 0 %23 = OpTypePointer Private %6 %26 = OpTypePointer Function %17 %29 = OpTypePointer Private %17 %33 = OpConstant %6 4 %200 = OpConstant %15 4 %35 = OpConstant %15 10 %36 = OpTypeArray %6 %35 %37 = OpTypePointer Private %36 %38 = OpVariable %37 Private %54 = OpTypeFloat 32 %55 = OpTypeVector %54 4 %56 = OpTypePointer Private %55 %57 = OpVariable %56 Private %59 = OpTypeVector %54 3 %60 = OpTypeMatrix %59 2 %61 = OpTypePointer Private %60 %62 = OpVariable %61 Private %64 = OpTypePointer Private %54 %69 = OpConstant %54 2 %71 = OpConstant %6 1 %72 = OpConstant %6 2 %201 = OpConstant %15 2 %73 = OpConstant %6 3 %202 = OpConstant %15 3 %203 = OpConstant %6 1 %204 = OpConstant %6 9 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %12 = OpFunction %2 None %8 %9 = OpFunctionParameter %7 %10 = OpFunctionParameter %102 %11 = OpFunctionParameter %7 %13 = OpLabel %14 = OpVariable %7 Function %27 = OpVariable %26 Function %22 = OpLoad %6 %11 %1002 = OpULessThanEqual %100 %22 %33 %2002 = OpSelect %6 %1002 %22 %33 %24 = OpAccessChain %23 %20 %21 %2002 %25 = OpLoad %6 %24 OpStore %14 %25 %28 = OpLoad %15 %10 %1003 = OpULessThanEqual %100 %28 %200 %2003 = OpSelect %15 %1003 %28 %200 %30 = OpAccessChain %29 %20 %2003 %31 = OpLoad %17 %30 OpStore %27 %31 %32 = OpLoad %6 %9 %1004 = OpULessThanEqual %100 %32 %33 %2004 = OpSelect %6 %1004 %32 %33 %34 = OpInBoundsAccessChain %7 %27 %2004 OpStore %34 %33 %39 = OpLoad %6 %9 %40 = OpAccessChain %23 %38 %33 %41 = OpLoad %6 %40 %1006 = OpULessThanEqual %100 %39 %204 %2006 = OpSelect %6 %1006 %39 %204 %42 = OpInBoundsAccessChain %23 %38 %2006 OpStore %42 %41 %43 = OpLoad %15 %10 %44 = OpLoad %6 %11 %45 = OpLoad %6 %9 %46 = OpLoad %15 %10 %47 = OpIAdd %6 %45 %46 %1007 = OpULessThanEqual %100 %47 %204 %2007 = OpSelect %6 %1007 %47 %204 %48 = OpAccessChain %23 %38 %2007 %49 = OpLoad %6 %48 %1008 = OpULessThanEqual %100 %43 %200 %2008 = OpSelect %15 %1008 %43 %200 %1009 = OpULessThanEqual %100 %44 %33 %2009 = OpSelect %6 %1009 %44 %33 %50 = OpInBoundsAccessChain %23 %20 %2008 %2009 %51 = OpLoad %6 %50 %52 = OpIAdd %6 %51 %49 %1010 = OpULessThanEqual %100 %43 %200 %2010 = OpSelect %15 %1010 %43 %200 %1011 = OpULessThanEqual %100 %44 %33 %2011 = OpSelect %6 %1011 %44 %33 %53 = OpAccessChain %23 %20 %2010 %2011 OpStore %53 %52 %58 = OpLoad %15 %10 %63 = OpLoad %6 %11 %1013 = OpULessThanEqual %100 %63 %72 %2013 = OpSelect %6 %1013 %63 %72 %65 = OpAccessChain %64 %62 %21 %2013 %1014 = OpULessThanEqual %100 %45 %71 %2014 = OpSelect %6 %1014 %45 %71 %1015 = OpULessThanEqual %100 %46 %201 %2015 = OpSelect %15 %1015 %46 %201 %101 = OpAccessChain %64 %62 %2014 %2015 %66 = OpLoad %54 %65 %1016 = OpULessThanEqual %100 %58 %202 %2016 = OpSelect %15 %1016 %58 %202 %67 = OpAccessChain %64 %57 %2016 OpStore %67 %66 %68 = OpLoad %6 %9 %1017 = OpULessThanEqual %100 %68 %73 %2017 = OpSelect %6 %1017 %68 %73 %70 = OpInBoundsAccessChain %64 %57 %2017 OpStore %70 %69 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_livesafe_code, context2.get())); } TEST(TransformationAddFunctionTest, LivesafeCanCallLivesafe) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; std::vector instructions; instructions.push_back( MakeInstructionMessage(spv::Op::OpFunction, 2, 8, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_TYPE_ID, {3}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 9, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpFunctionCall, 2, 11, {{SPV_OPERAND_TYPE_ID, {6}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpReturn, 0, 0, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})); spvtools::ValidatorOptions validator_options; const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context1( MakeUnique(context1.get()), validator_options); TransformationContext transformation_context2( MakeUnique(context2.get()), validator_options); // Mark function 6 as livesafe. transformation_context2.GetFactManager()->AddFactFunctionIsLivesafe(6); TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); ApplyAndCheckFreshIds(add_dead_function, context1.get(), &transformation_context1); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); // The function should not be deemed livesafe ASSERT_FALSE(transformation_context1.GetFactManager()->FunctionIsLivesafe(8)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context1.get(), transformation_context1, 8, 0)); std::string added_as_live_or_dead_code = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel OpReturn OpFunctionEnd %8 = OpFunction %2 None %3 %9 = OpLabel %11 = OpFunctionCall %2 %6 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_live_or_dead_code, context1.get())); TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 0, {}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context2.get(), transformation_context2)); ApplyAndCheckFreshIds(add_livesafe_function, context2.get(), &transformation_context2); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context2.get(), validator_options, kConsoleMessageConsumer)); // The function should be deemed livesafe ASSERT_TRUE(transformation_context2.GetFactManager()->FunctionIsLivesafe(8)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context2.get(), transformation_context2, 8, 0)); ASSERT_TRUE(IsEqual(env, added_as_live_or_dead_code, context2.get())); } TEST(TransformationAddFunctionTest, LivesafeOnlyCallsLivesafe) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel OpKill OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; const auto consumer = nullptr; std::vector instructions; instructions.push_back( MakeInstructionMessage(spv::Op::OpFunction, 2, 8, {{SPV_OPERAND_TYPE_FUNCTION_CONTROL, {uint32_t(spv::FunctionControlMask::MaskNone)}}, {SPV_OPERAND_TYPE_TYPE_ID, {3}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpLabel, 0, 9, {})); instructions.push_back(MakeInstructionMessage(spv::Op::OpFunctionCall, 2, 11, {{SPV_OPERAND_TYPE_ID, {6}}})); instructions.push_back(MakeInstructionMessage(spv::Op::OpReturn, 0, 0, {})); instructions.push_back( MakeInstructionMessage(spv::Op::OpFunctionEnd, 0, 0, {})); spvtools::ValidatorOptions validator_options; const auto context1 = BuildModule(env, consumer, shader, kFuzzAssembleOption); const auto context2 = BuildModule(env, consumer, shader, kFuzzAssembleOption); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); TransformationContext transformation_context1( MakeUnique(context1.get()), validator_options); TransformationContext transformation_context2( MakeUnique(context2.get()), validator_options); TransformationAddFunction add_dead_function(instructions); ASSERT_TRUE( add_dead_function.IsApplicable(context1.get(), transformation_context1)); ApplyAndCheckFreshIds(add_dead_function, context1.get(), &transformation_context1); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed( context1.get(), validator_options, kConsoleMessageConsumer)); // The function should not be deemed livesafe ASSERT_FALSE(transformation_context1.GetFactManager()->FunctionIsLivesafe(8)); // All variables/parameters in the function should be deemed irrelevant. ASSERT_TRUE(AllVariablesAndParametersExceptLoopLimiterAreIrrelevant( context1.get(), transformation_context1, 8, 0)); std::string added_as_dead_code = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel OpKill OpFunctionEnd %8 = OpFunction %2 None %3 %9 = OpLabel %11 = OpFunctionCall %2 %6 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, added_as_dead_code, context1.get())); TransformationAddFunction add_livesafe_function(instructions, 0, 0, {}, 0, {}); ASSERT_FALSE(add_livesafe_function.IsApplicable(context2.get(), transformation_context2)); } TEST(TransformationAddFunctionTest, LoopLimitersBackEdgeBlockEndsWithConditional1) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; std::string donor = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel OpLoopMerge %14 %15 None OpBranch %15 %15 = OpLabel %17 = OpLoad %8 %10 %20 = OpSLessThan %19 %17 %18 %21 = OpLoad %8 %10 %23 = OpIAdd %8 %21 %22 OpStore %10 %23 OpBranchConditional %20 %12 %14 %14 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector instructions = GetInstructionsForFunction(env, consumer, donor, 6); protobufs::LoopLimiterInfo loop_limiter_info; loop_limiter_info.set_loop_header_id(12); loop_limiter_info.set_load_id(102); loop_limiter_info.set_increment_id(103); loop_limiter_info.set_compare_id(104); loop_limiter_info.set_logical_op_id(105); TransformationAddFunction add_livesafe_function(instructions, 100, 32, {loop_limiter_info}, 0, {}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(add_livesafe_function, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string expected = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %100 = OpVariable %29 Function %30 %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel OpLoopMerge %14 %15 None OpBranch %15 %15 = OpLabel %17 = OpLoad %8 %10 %20 = OpSLessThan %19 %17 %18 %21 = OpLoad %8 %10 %23 = OpIAdd %8 %21 %22 OpStore %10 %23 %102 = OpLoad %28 %100 %103 = OpIAdd %28 %102 %31 OpStore %100 %103 %104 = OpULessThan %19 %102 %32 %105 = OpLogicalAnd %19 %20 %104 OpBranchConditional %105 %12 %14 %14 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, expected, context.get())); } TEST(TransformationAddFunctionTest, LoopLimitersBackEdgeBlockEndsWithConditional2) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; std::string donor = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel OpLoopMerge %14 %15 None OpBranch %15 %15 = OpLabel %17 = OpLoad %8 %10 %20 = OpSLessThan %19 %17 %18 %21 = OpLoad %8 %10 %23 = OpIAdd %8 %21 %22 OpStore %10 %23 %50 = OpLogicalNot %19 %20 OpBranchConditional %50 %14 %12 %14 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector instructions = GetInstructionsForFunction(env, consumer, donor, 6); protobufs::LoopLimiterInfo loop_limiter_info; loop_limiter_info.set_loop_header_id(12); loop_limiter_info.set_load_id(102); loop_limiter_info.set_increment_id(103); loop_limiter_info.set_compare_id(104); loop_limiter_info.set_logical_op_id(105); TransformationAddFunction add_livesafe_function(instructions, 100, 32, {loop_limiter_info}, 0, {}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(add_livesafe_function, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string expected = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %100 = OpVariable %29 Function %30 %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel OpLoopMerge %14 %15 None OpBranch %15 %15 = OpLabel %17 = OpLoad %8 %10 %20 = OpSLessThan %19 %17 %18 %21 = OpLoad %8 %10 %23 = OpIAdd %8 %21 %22 OpStore %10 %23 %50 = OpLogicalNot %19 %20 %102 = OpLoad %28 %100 %103 = OpIAdd %28 %102 %31 OpStore %100 %103 %104 = OpUGreaterThanEqual %19 %102 %32 %105 = OpLogicalOr %19 %50 %104 OpBranchConditional %105 %14 %12 %14 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, expected, context.get())); } TEST(TransformationAddFunctionTest, LoopLimitersHeaderIsBackEdgeBlock) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; std::string donor = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel %17 = OpLoad %8 %10 %20 = OpSLessThan %19 %17 %18 %21 = OpLoad %8 %10 %23 = OpIAdd %8 %21 %22 OpStore %10 %23 %50 = OpLogicalNot %19 %20 OpLoopMerge %14 %12 None OpBranchConditional %50 %14 %12 %14 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector instructions = GetInstructionsForFunction(env, consumer, donor, 6); protobufs::LoopLimiterInfo loop_limiter_info; loop_limiter_info.set_loop_header_id(12); loop_limiter_info.set_load_id(102); loop_limiter_info.set_increment_id(103); loop_limiter_info.set_compare_id(104); loop_limiter_info.set_logical_op_id(105); TransformationAddFunction add_livesafe_function(instructions, 100, 32, {loop_limiter_info}, 0, {}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(add_livesafe_function, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string expected = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %100 = OpVariable %29 Function %30 %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel %17 = OpLoad %8 %10 %20 = OpSLessThan %19 %17 %18 %21 = OpLoad %8 %10 %23 = OpIAdd %8 %21 %22 OpStore %10 %23 %50 = OpLogicalNot %19 %20 %102 = OpLoad %28 %100 %103 = OpIAdd %28 %102 %31 OpStore %100 %103 %104 = OpUGreaterThanEqual %19 %102 %32 %105 = OpLogicalOr %19 %50 %104 OpLoopMerge %14 %12 None OpBranchConditional %105 %14 %12 %14 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, expected, context.get())); } TEST(TransformationAddFunctionTest, InfiniteLoop) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; std::string donor = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %22 = OpConstant %8 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel OpLoopMerge %14 %12 None OpBranch %12 %14 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector instructions = GetInstructionsForFunction(env, consumer, donor, 6); protobufs::LoopLimiterInfo loop_limiter_info; loop_limiter_info.set_loop_header_id(12); loop_limiter_info.set_load_id(102); loop_limiter_info.set_increment_id(103); loop_limiter_info.set_compare_id(104); loop_limiter_info.set_logical_op_id(105); TransformationAddFunction add_livesafe_function(instructions, 100, 32, {loop_limiter_info}, 0, {}); // To make sure the loop's merge block is reachable, it must be dominated by // the loop header. ASSERT_FALSE(add_livesafe_function.IsApplicable(context.get(), transformation_context)); } TEST(TransformationAddFunctionTest, UnreachableContinueConstruct) { // This captures the case where the loop's continue construct is statically // unreachable. In this case the loop cannot iterate and so we do not add // a loop limiter. (The reason we do not just add one anyway is that // detecting which block would be the back-edge block is difficult in the // absence of reliable dominance information.) std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %23 = OpConstant %8 1 %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; std::string donor = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %23 = OpConstant %8 1 %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel OpLoopMerge %14 %15 None OpBranch %16 %16 = OpLabel %17 = OpLoad %8 %10 %20 = OpSLessThan %19 %17 %18 OpBranchConditional %20 %13 %14 %13 = OpLabel OpBranch %14 %15 = OpLabel %22 = OpLoad %8 %10 %24 = OpIAdd %8 %22 %23 OpStore %10 %24 OpBranch %12 %14 = OpLabel OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector instructions = GetInstructionsForFunction(env, consumer, donor, 6); protobufs::LoopLimiterInfo loop_limiter_info; loop_limiter_info.set_loop_header_id(12); loop_limiter_info.set_load_id(102); loop_limiter_info.set_increment_id(103); loop_limiter_info.set_compare_id(104); loop_limiter_info.set_logical_op_id(105); TransformationAddFunction add_livesafe_function(instructions, 100, 32, {loop_limiter_info}, 0, {}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(add_livesafe_function, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string expected = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %11 = OpConstant %8 0 %18 = OpConstant %8 10 %19 = OpTypeBool %23 = OpConstant %8 1 %26 = OpConstantTrue %19 %27 = OpConstantFalse %19 %28 = OpTypeInt 32 0 %29 = OpTypePointer Function %28 %30 = OpConstant %28 0 %31 = OpConstant %28 1 %32 = OpConstant %28 5 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %100 = OpVariable %29 Function %30 %10 = OpVariable %9 Function OpStore %10 %11 OpBranch %12 %12 = OpLabel OpLoopMerge %14 %15 None OpBranch %16 %16 = OpLabel %17 = OpLoad %8 %10 %20 = OpSLessThan %19 %17 %18 OpBranchConditional %20 %13 %14 %13 = OpLabel OpBranch %14 %15 = OpLabel %22 = OpLoad %8 %10 %24 = OpIAdd %8 %22 %23 OpStore %10 %24 OpBranch %12 %14 = OpLabel OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, expected, context.get())); } TEST(TransformationAddFunctionTest, LoopLimitersAndOpPhi1) { // This captures the scenario where breaking a loop due to a loop limiter // requires patching up OpPhi instructions occurring at the loop merge block. std::string shader = R"( OpCapability Shader %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 %50 = OpTypeInt 32 0 %51 = OpConstant %50 0 %52 = OpConstant %50 1 %53 = OpTypePointer Function %50 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %19 = OpConstant %6 100 %20 = OpTypeBool %23 = OpConstant %6 20 %28 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; std::string donor = R"( OpCapability Shader %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 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %19 = OpConstant %6 100 %20 = OpTypeBool %23 = OpConstant %6 20 %28 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel %36 = OpFunctionCall %6 %8 OpReturn OpFunctionEnd %8 = OpFunction %6 None %7 %9 = OpLabel %11 = OpVariable %10 Function OpStore %11 %12 OpBranch %13 %13 = OpLabel %37 = OpPhi %6 %12 %9 %32 %16 OpLoopMerge %15 %16 None OpBranch %17 %17 = OpLabel %21 = OpSLessThan %20 %37 %19 OpBranchConditional %21 %14 %15 %14 = OpLabel %24 = OpSGreaterThan %20 %37 %23 OpSelectionMerge %26 None OpBranchConditional %24 %25 %26 %25 = OpLabel %29 = OpIAdd %6 %37 %28 OpStore %11 %29 OpBranch %15 %26 = OpLabel OpBranch %16 %16 = OpLabel %32 = OpIAdd %6 %37 %28 OpStore %11 %32 OpBranch %13 %15 = OpLabel %38 = OpPhi %6 %37 %17 %29 %25 OpReturnValue %38 OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // Make a sequence of instruction messages corresponding to function %8 in // |donor|. std::vector instructions = GetInstructionsForFunction(env, consumer, donor, 8); protobufs::LoopLimiterInfo loop_limiter_info; loop_limiter_info.set_loop_header_id(13); loop_limiter_info.set_load_id(102); loop_limiter_info.set_increment_id(103); loop_limiter_info.set_compare_id(104); loop_limiter_info.set_logical_op_id(105); TransformationAddFunction no_op_phi_data(instructions, 100, 28, {loop_limiter_info}, 0, {}); // The loop limiter info is not good enough; it does not include ids to patch // up the OpPhi at the loop merge. ASSERT_FALSE( no_op_phi_data.IsApplicable(context.get(), transformation_context)); // Add a phi id for the new edge from the loop back edge block to the loop // merge. loop_limiter_info.add_phi_id(28); TransformationAddFunction with_op_phi_data(instructions, 100, 28, {loop_limiter_info}, 0, {}); ASSERT_TRUE( with_op_phi_data.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(with_op_phi_data, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string expected = R"( OpCapability Shader %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 %50 = OpTypeInt 32 0 %51 = OpConstant %50 0 %52 = OpConstant %50 1 %53 = OpTypePointer Function %50 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %19 = OpConstant %6 100 %20 = OpTypeBool %23 = OpConstant %6 20 %28 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %8 = OpFunction %6 None %7 %9 = OpLabel %100 = OpVariable %53 Function %51 %11 = OpVariable %10 Function OpStore %11 %12 OpBranch %13 %13 = OpLabel %37 = OpPhi %6 %12 %9 %32 %16 OpLoopMerge %15 %16 None OpBranch %17 %17 = OpLabel %21 = OpSLessThan %20 %37 %19 OpBranchConditional %21 %14 %15 %14 = OpLabel %24 = OpSGreaterThan %20 %37 %23 OpSelectionMerge %26 None OpBranchConditional %24 %25 %26 %25 = OpLabel %29 = OpIAdd %6 %37 %28 OpStore %11 %29 OpBranch %15 %26 = OpLabel OpBranch %16 %16 = OpLabel %32 = OpIAdd %6 %37 %28 OpStore %11 %32 %102 = OpLoad %50 %100 %103 = OpIAdd %50 %102 %52 OpStore %100 %103 %104 = OpUGreaterThanEqual %20 %102 %28 OpBranchConditional %104 %15 %13 %15 = OpLabel %38 = OpPhi %6 %37 %17 %29 %25 %28 %16 OpReturnValue %38 OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, expected, context.get())); } TEST(TransformationAddFunctionTest, LoopLimitersAndOpPhi2) { // This captures the scenario where the loop merge block already has an OpPhi // with the loop back edge block as a predecessor. std::string shader = R"( OpCapability Shader %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 %50 = OpTypeInt 32 0 %51 = OpConstant %50 0 %52 = OpConstant %50 1 %53 = OpTypePointer Function %50 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %19 = OpConstant %6 100 %20 = OpTypeBool %60 = OpConstantTrue %20 %23 = OpConstant %6 20 %28 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; std::string donor = R"( OpCapability Shader %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 %50 = OpTypeInt 32 0 %51 = OpConstant %50 0 %52 = OpConstant %50 1 %53 = OpTypePointer Function %50 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %19 = OpConstant %6 100 %20 = OpTypeBool %60 = OpConstantTrue %20 %23 = OpConstant %6 20 %28 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %8 = OpFunction %6 None %7 %9 = OpLabel %11 = OpVariable %10 Function OpStore %11 %12 OpBranch %13 %13 = OpLabel %37 = OpPhi %6 %12 %9 %32 %16 OpLoopMerge %15 %16 None OpBranch %17 %17 = OpLabel %21 = OpSLessThan %20 %37 %19 OpBranchConditional %21 %14 %15 %14 = OpLabel %24 = OpSGreaterThan %20 %37 %23 OpSelectionMerge %26 None OpBranchConditional %24 %25 %26 %25 = OpLabel %29 = OpIAdd %6 %37 %28 OpStore %11 %29 OpBranch %15 %26 = OpLabel OpBranch %16 %16 = OpLabel %32 = OpIAdd %6 %37 %28 OpStore %11 %32 OpBranchConditional %60 %15 %13 %15 = OpLabel %38 = OpPhi %6 %37 %17 %29 %25 %23 %16 OpReturnValue %38 OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // Make a sequence of instruction messages corresponding to function %8 in // |donor|. std::vector instructions = GetInstructionsForFunction(env, consumer, donor, 8); protobufs::LoopLimiterInfo loop_limiter_info; loop_limiter_info.set_loop_header_id(13); loop_limiter_info.set_load_id(102); loop_limiter_info.set_increment_id(103); loop_limiter_info.set_compare_id(104); loop_limiter_info.set_logical_op_id(105); TransformationAddFunction transformation(instructions, 100, 28, {loop_limiter_info}, 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 expected = R"( OpCapability Shader %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 %50 = OpTypeInt 32 0 %51 = OpConstant %50 0 %52 = OpConstant %50 1 %53 = OpTypePointer Function %50 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %19 = OpConstant %6 100 %20 = OpTypeBool %60 = OpConstantTrue %20 %23 = OpConstant %6 20 %28 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %8 = OpFunction %6 None %7 %9 = OpLabel %100 = OpVariable %53 Function %51 %11 = OpVariable %10 Function OpStore %11 %12 OpBranch %13 %13 = OpLabel %37 = OpPhi %6 %12 %9 %32 %16 OpLoopMerge %15 %16 None OpBranch %17 %17 = OpLabel %21 = OpSLessThan %20 %37 %19 OpBranchConditional %21 %14 %15 %14 = OpLabel %24 = OpSGreaterThan %20 %37 %23 OpSelectionMerge %26 None OpBranchConditional %24 %25 %26 %25 = OpLabel %29 = OpIAdd %6 %37 %28 OpStore %11 %29 OpBranch %15 %26 = OpLabel OpBranch %16 %16 = OpLabel %32 = OpIAdd %6 %37 %28 OpStore %11 %32 %102 = OpLoad %50 %100 %103 = OpIAdd %50 %102 %52 OpStore %100 %103 %104 = OpUGreaterThanEqual %20 %102 %28 %105 = OpLogicalOr %20 %60 %104 OpBranchConditional %105 %15 %13 %15 = OpLabel %38 = OpPhi %6 %37 %17 %29 %25 %23 %16 OpReturnValue %38 OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, expected, context.get())); } TEST(TransformationAddFunctionTest, StaticallyOutOfBoundsArrayAccess) { std::string shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypeInt 32 0 %10 = OpConstant %9 3 %11 = OpTypeArray %8 %10 %12 = OpTypePointer Private %11 %13 = OpVariable %12 Private %14 = OpConstant %8 3 %20 = OpConstant %8 2 %15 = OpConstant %8 1 %21 = OpTypeBool %16 = OpTypePointer Private %8 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd )"; std::string donor = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypeInt 32 0 %10 = OpConstant %9 3 %11 = OpTypeArray %8 %10 %12 = OpTypePointer Private %11 %13 = OpVariable %12 Private %14 = OpConstant %8 3 %15 = OpConstant %8 1 %16 = OpTypePointer Private %8 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %17 = OpAccessChain %16 %13 %14 OpStore %17 %15 OpReturn OpFunctionEnd )"; const auto env = SPV_ENV_UNIVERSAL_1_4; 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); // Make a sequence of instruction messages corresponding to function %6 in // |donor|. std::vector instructions = GetInstructionsForFunction(env, consumer, donor, 6); TransformationAddFunction add_livesafe_function( instructions, 0, 0, {}, 0, {MakeAccessClampingInfo(17, {{100, 101}})}); ASSERT_TRUE(add_livesafe_function.IsApplicable(context.get(), transformation_context)); ApplyAndCheckFreshIds(add_livesafe_function, context.get(), &transformation_context); ASSERT_TRUE(fuzzerutil::IsValidAndWellFormed(context.get(), validator_options, kConsoleMessageConsumer)); std::string expected = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource ESSL 310 %2 = OpTypeVoid %3 = OpTypeFunction %2 %8 = OpTypeInt 32 1 %9 = OpTypeInt 32 0 %10 = OpConstant %9 3 %11 = OpTypeArray %8 %10 %12 = OpTypePointer Private %11 %13 = OpVariable %12 Private %14 = OpConstant %8 3 %20 = OpConstant %8 2 %15 = OpConstant %8 1 %21 = OpTypeBool %16 = OpTypePointer Private %8 %4 = OpFunction %2 None %3 %5 = OpLabel OpReturn OpFunctionEnd %6 = OpFunction %2 None %3 %7 = OpLabel %100 = OpULessThanEqual %21 %14 %20 %101 = OpSelect %8 %100 %14 %20 %17 = OpAccessChain %16 %13 %101 OpStore %17 %15 OpReturn OpFunctionEnd )"; ASSERT_TRUE(IsEqual(env, expected, context.get())); } } // namespace } // namespace fuzz } // namespace spvtools