// Copyright (c) 2018 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 #include #include #include "gmock/gmock.h" #include "source/opt/loop_unroller.h" #include "source/opt/loop_utils.h" #include "source/opt/pass.h" #include "test/opt/assembly_builder.h" #include "test/opt/function_utils.h" #include "test/opt/pass_fixture.h" #include "test/opt/pass_utils.h" namespace spvtools { namespace opt { namespace { using ::testing::UnorderedElementsAre; using PassClassTest = PassTest<::testing::Test>; /* Generated from the following GLSL #version 330 core layout(location = 0) out vec4 c; void main() { float x[4]; for (int i = 0; i < 4; ++i) { x[i] = 1.0f; } } */ TEST_F(PassClassTest, SimpleFullyUnrollTest) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" %3 OpExecutionMode %2 OriginUpperLeft OpSource GLSL 330 OpName %2 "main" OpName %5 "x" OpName %3 "c" OpDecorate %3 Location 0 %6 = OpTypeVoid %7 = OpTypeFunction %6 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %10 = OpConstant %8 0 %11 = OpConstant %8 4 %12 = OpTypeBool %13 = OpTypeFloat 32 %14 = OpTypeInt 32 0 %15 = OpConstant %14 4 %16 = OpTypeArray %13 %15 %17 = OpTypePointer Function %16 %18 = OpConstant %13 1 %19 = OpTypePointer Function %13 %20 = OpConstant %8 1 %21 = OpTypeVector %13 4 %22 = OpTypePointer Output %21 %3 = OpVariable %22 Output %2 = OpFunction %6 None %7 %23 = OpLabel %5 = OpVariable %17 Function OpBranch %24 %24 = OpLabel %35 = OpPhi %8 %10 %23 %34 %26 OpLoopMerge %25 %26 Unroll OpBranch %27 %27 = OpLabel %29 = OpSLessThan %12 %35 %11 OpBranchConditional %29 %30 %25 %30 = OpLabel %32 = OpAccessChain %19 %5 %35 OpStore %32 %18 OpBranch %26 %26 = OpLabel %34 = OpIAdd %8 %35 %20 OpBranch %24 %25 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" %3 OpExecutionMode %2 OriginUpperLeft OpSource GLSL 330 OpName %2 "main" OpName %4 "x" OpName %3 "c" OpDecorate %3 Location 0 %5 = OpTypeVoid %6 = OpTypeFunction %5 %7 = OpTypeInt 32 1 %8 = OpTypePointer Function %7 %9 = OpConstant %7 0 %10 = OpConstant %7 4 %11 = OpTypeBool %12 = OpTypeFloat 32 %13 = OpTypeInt 32 0 %14 = OpConstant %13 4 %15 = OpTypeArray %12 %14 %16 = OpTypePointer Function %15 %17 = OpConstant %12 1 %18 = OpTypePointer Function %12 %19 = OpConstant %7 1 %20 = OpTypeVector %12 4 %21 = OpTypePointer Output %20 %3 = OpVariable %21 Output %2 = OpFunction %5 None %6 %22 = OpLabel %4 = OpVariable %16 Function OpBranch %23 %23 = OpLabel OpBranch %28 %28 = OpLabel %29 = OpSLessThan %11 %9 %10 OpBranch %30 %30 = OpLabel %31 = OpAccessChain %18 %4 %9 OpStore %31 %17 OpBranch %26 %26 = OpLabel %25 = OpIAdd %7 %9 %19 OpBranch %32 %32 = OpLabel OpBranch %34 %34 = OpLabel %35 = OpSLessThan %11 %25 %10 OpBranch %36 %36 = OpLabel %37 = OpAccessChain %18 %4 %25 OpStore %37 %17 OpBranch %38 %38 = OpLabel %39 = OpIAdd %7 %25 %19 OpBranch %40 %40 = OpLabel OpBranch %42 %42 = OpLabel %43 = OpSLessThan %11 %39 %10 OpBranch %44 %44 = OpLabel %45 = OpAccessChain %18 %4 %39 OpStore %45 %17 OpBranch %46 %46 = OpLabel %47 = OpIAdd %7 %39 %19 OpBranch %48 %48 = OpLabel OpBranch %50 %50 = OpLabel %51 = OpSLessThan %11 %47 %10 OpBranch %52 %52 = OpLabel %53 = OpAccessChain %18 %4 %47 OpStore %53 %17 OpBranch %54 %54 = OpLabel %55 = OpIAdd %7 %47 %19 OpBranch %27 %27 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(text, output, false); } template class PartialUnrollerTestPass : public Pass { public: PartialUnrollerTestPass() : Pass() {} const char* name() const override { return "Loop unroller"; } Status Process() override { for (Function& f : *context()->module()) { LoopDescriptor& loop_descriptor = *context()->GetLoopDescriptor(&f); for (auto& loop : loop_descriptor) { LoopUtils loop_utils{context(), &loop}; loop_utils.PartiallyUnroll(factor); } } return Pass::Status::SuccessWithChange; } }; /* Generated from the following GLSL #version 330 core layout(location = 0) out vec4 c; void main() { float x[10]; for (int i = 0; i < 10; ++i) { x[i] = 1.0f; } } */ TEST_F(PassClassTest, SimplePartialUnroll) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" %3 OpExecutionMode %2 OriginUpperLeft OpSource GLSL 330 OpName %2 "main" OpName %5 "x" OpName %3 "c" OpDecorate %3 Location 0 %6 = OpTypeVoid %7 = OpTypeFunction %6 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %10 = OpConstant %8 0 %11 = OpConstant %8 10 %12 = OpTypeBool %13 = OpTypeFloat 32 %14 = OpTypeInt 32 0 %15 = OpConstant %14 10 %16 = OpTypeArray %13 %15 %17 = OpTypePointer Function %16 %18 = OpConstant %13 1 %19 = OpTypePointer Function %13 %20 = OpConstant %8 1 %21 = OpTypeVector %13 4 %22 = OpTypePointer Output %21 %3 = OpVariable %22 Output %2 = OpFunction %6 None %7 %23 = OpLabel %5 = OpVariable %17 Function OpBranch %24 %24 = OpLabel %35 = OpPhi %8 %10 %23 %34 %26 OpLoopMerge %25 %26 Unroll OpBranch %27 %27 = OpLabel %29 = OpSLessThan %12 %35 %11 OpBranchConditional %29 %30 %25 %30 = OpLabel %32 = OpAccessChain %19 %5 %35 OpStore %32 %18 OpBranch %26 %26 = OpLabel %34 = OpIAdd %8 %35 %20 OpBranch %24 %25 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" %3 OpExecutionMode %2 OriginUpperLeft OpSource GLSL 330 OpName %2 "main" OpName %4 "x" OpName %3 "c" OpDecorate %3 Location 0 %5 = OpTypeVoid %6 = OpTypeFunction %5 %7 = OpTypeInt 32 1 %8 = OpTypePointer Function %7 %9 = OpConstant %7 0 %10 = OpConstant %7 10 %11 = OpTypeBool %12 = OpTypeFloat 32 %13 = OpTypeInt 32 0 %14 = OpConstant %13 10 %15 = OpTypeArray %12 %14 %16 = OpTypePointer Function %15 %17 = OpConstant %12 1 %18 = OpTypePointer Function %12 %19 = OpConstant %7 1 %20 = OpTypeVector %12 4 %21 = OpTypePointer Output %20 %3 = OpVariable %21 Output %2 = OpFunction %5 None %6 %22 = OpLabel %4 = OpVariable %16 Function OpBranch %23 %23 = OpLabel %24 = OpPhi %7 %9 %22 %39 %38 OpLoopMerge %27 %38 DontUnroll OpBranch %28 %28 = OpLabel %29 = OpSLessThan %11 %24 %10 OpBranchConditional %29 %30 %27 %30 = OpLabel %31 = OpAccessChain %18 %4 %24 OpStore %31 %17 OpBranch %26 %26 = OpLabel %25 = OpIAdd %7 %24 %19 OpBranch %32 %32 = OpLabel OpBranch %34 %34 = OpLabel %35 = OpSLessThan %11 %25 %10 OpBranch %36 %36 = OpLabel %37 = OpAccessChain %18 %4 %25 OpStore %37 %17 OpBranch %38 %38 = OpLabel %39 = OpIAdd %7 %25 %19 OpBranch %23 %27 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck>(text, output, false); } /* Generated from the following GLSL #version 330 core layout(location = 0) out vec4 c; void main() { float x[10]; for (int i = 0; i < 10; ++i) { x[i] = 1.0f; } } */ TEST_F(PassClassTest, SimpleUnevenPartialUnroll) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" %3 OpExecutionMode %2 OriginUpperLeft OpSource GLSL 330 OpName %2 "main" OpName %5 "x" OpName %3 "c" OpDecorate %3 Location 0 %6 = OpTypeVoid %7 = OpTypeFunction %6 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %10 = OpConstant %8 0 %11 = OpConstant %8 10 %12 = OpTypeBool %13 = OpTypeFloat 32 %14 = OpTypeInt 32 0 %15 = OpConstant %14 10 %16 = OpTypeArray %13 %15 %17 = OpTypePointer Function %16 %18 = OpConstant %13 1 %19 = OpTypePointer Function %13 %20 = OpConstant %8 1 %21 = OpTypeVector %13 4 %22 = OpTypePointer Output %21 %3 = OpVariable %22 Output %2 = OpFunction %6 None %7 %23 = OpLabel %5 = OpVariable %17 Function OpBranch %24 %24 = OpLabel %35 = OpPhi %8 %10 %23 %34 %26 OpLoopMerge %25 %26 Unroll OpBranch %27 %27 = OpLabel %29 = OpSLessThan %12 %35 %11 OpBranchConditional %29 %30 %25 %30 = OpLabel %32 = OpAccessChain %19 %5 %35 OpStore %32 %18 OpBranch %26 %26 = OpLabel %34 = OpIAdd %8 %35 %20 OpBranch %24 %25 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" %3 OpExecutionMode %2 OriginUpperLeft OpSource GLSL 330 OpName %2 "main" OpName %4 "x" OpName %3 "c" OpDecorate %3 Location 0 %5 = OpTypeVoid %6 = OpTypeFunction %5 %7 = OpTypeInt 32 1 %8 = OpTypePointer Function %7 %9 = OpConstant %7 0 %10 = OpConstant %7 10 %11 = OpTypeBool %12 = OpTypeFloat 32 %13 = OpTypeInt 32 0 %14 = OpConstant %13 10 %15 = OpTypeArray %12 %14 %16 = OpTypePointer Function %15 %17 = OpConstant %12 1 %18 = OpTypePointer Function %12 %19 = OpConstant %7 1 %20 = OpTypeVector %12 4 %21 = OpTypePointer Output %20 %3 = OpVariable %21 Output %58 = OpConstant %13 1 %2 = OpFunction %5 None %6 %22 = OpLabel %4 = OpVariable %16 Function OpBranch %23 %23 = OpLabel %24 = OpPhi %7 %9 %22 %25 %26 OpLoopMerge %32 %26 Unroll OpBranch %28 %28 = OpLabel %29 = OpSLessThan %11 %24 %58 OpBranchConditional %29 %30 %32 %30 = OpLabel %31 = OpAccessChain %18 %4 %24 OpStore %31 %17 OpBranch %26 %26 = OpLabel %25 = OpIAdd %7 %24 %19 OpBranch %23 %32 = OpLabel OpBranch %33 %33 = OpLabel %34 = OpPhi %7 %24 %32 %57 %56 OpLoopMerge %41 %56 DontUnroll OpBranch %35 %35 = OpLabel %36 = OpSLessThan %11 %34 %10 OpBranchConditional %36 %37 %41 %37 = OpLabel %38 = OpAccessChain %18 %4 %34 OpStore %38 %17 OpBranch %39 %39 = OpLabel %40 = OpIAdd %7 %34 %19 OpBranch %42 %42 = OpLabel OpBranch %44 %44 = OpLabel %45 = OpSLessThan %11 %40 %10 OpBranch %46 %46 = OpLabel %47 = OpAccessChain %18 %4 %40 OpStore %47 %17 OpBranch %48 %48 = OpLabel %49 = OpIAdd %7 %40 %19 OpBranch %50 %50 = OpLabel OpBranch %52 %52 = OpLabel %53 = OpSLessThan %11 %49 %10 OpBranch %54 %54 = OpLabel %55 = OpAccessChain %18 %4 %49 OpStore %55 %17 OpBranch %56 %56 = OpLabel %57 = OpIAdd %7 %49 %19 OpBranch %33 %41 = OpLabel OpReturn %27 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); // By unrolling by a factor that doesn't divide evenly into the number of loop // iterations we perfom an additional transform when partially unrolling to // account for the remainder. SinglePassRunAndCheck>(text, output, false); } /* Generated from #version 410 core layout(location=0) flat in int upper_bound; void main() { float x[10]; for (int i = 2; i < 8; i+=2) { x[i] = i; } } */ TEST_F(PassClassTest, SimpleLoopIterationsCheck) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" %3 OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %5 "x" OpName %3 "upper_bound" OpDecorate %3 Flat OpDecorate %3 Location 0 %6 = OpTypeVoid %7 = OpTypeFunction %6 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %10 = OpConstant %8 2 %11 = OpConstant %8 8 %12 = OpTypeBool %13 = OpTypeFloat 32 %14 = OpTypeInt 32 0 %15 = OpConstant %14 10 %16 = OpTypeArray %13 %15 %17 = OpTypePointer Function %16 %18 = OpTypePointer Function %13 %19 = OpTypePointer Input %8 %3 = OpVariable %19 Input %2 = OpFunction %6 None %7 %20 = OpLabel %5 = OpVariable %17 Function OpBranch %21 %21 = OpLabel %34 = OpPhi %8 %10 %20 %33 %23 OpLoopMerge %22 %23 Unroll OpBranch %24 %24 = OpLabel %26 = OpSLessThan %12 %34 %11 OpBranchConditional %26 %27 %22 %27 = OpLabel %30 = OpConvertSToF %13 %34 %31 = OpAccessChain %18 %5 %34 OpStore %31 %30 OpBranch %23 %23 = OpLabel %33 = OpIAdd %8 %34 %10 OpBranch %21 %22 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; Function* f = spvtest::GetFunction(module, 2); LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); EXPECT_EQ(loop_descriptor.NumLoops(), 1u); Loop& loop = loop_descriptor.GetLoopByIndex(0); EXPECT_TRUE(loop.HasUnrollLoopControl()); BasicBlock* condition = loop.FindConditionBlock(); EXPECT_EQ(condition->id(), 24u); Instruction* induction = loop.FindConditionVariable(condition); EXPECT_EQ(induction->result_id(), 34u); LoopUtils loop_utils{context.get(), &loop}; EXPECT_TRUE(loop_utils.CanPerformUnroll()); size_t iterations = 0; EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(), &iterations)); EXPECT_EQ(iterations, 3u); } /* Generated from #version 410 core void main() { float x[10]; for (int i = -1; i < 6; i+=3) { x[i] = i; } } */ TEST_F(PassClassTest, SimpleLoopIterationsCheckSignedInit) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" %3 OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %5 "x" OpName %3 "upper_bound" OpDecorate %3 Flat OpDecorate %3 Location 0 %6 = OpTypeVoid %7 = OpTypeFunction %6 %8 = OpTypeInt 32 1 %9 = OpTypePointer Function %8 %10 = OpConstant %8 -1 %11 = OpConstant %8 6 %12 = OpTypeBool %13 = OpTypeFloat 32 %14 = OpTypeInt 32 0 %15 = OpConstant %14 10 %16 = OpTypeArray %13 %15 %17 = OpTypePointer Function %16 %18 = OpTypePointer Function %13 %19 = OpConstant %8 3 %20 = OpTypePointer Input %8 %3 = OpVariable %20 Input %2 = OpFunction %6 None %7 %21 = OpLabel %5 = OpVariable %17 Function OpBranch %22 %22 = OpLabel %35 = OpPhi %8 %10 %21 %34 %24 OpLoopMerge %23 %24 None OpBranch %25 %25 = OpLabel %27 = OpSLessThan %12 %35 %11 OpBranchConditional %27 %28 %23 %28 = OpLabel %31 = OpConvertSToF %13 %35 %32 = OpAccessChain %18 %5 %35 OpStore %32 %31 OpBranch %24 %24 = OpLabel %34 = OpIAdd %8 %35 %19 OpBranch %22 %23 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; Function* f = spvtest::GetFunction(module, 2); LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); EXPECT_EQ(loop_descriptor.NumLoops(), 1u); Loop& loop = loop_descriptor.GetLoopByIndex(0); EXPECT_FALSE(loop.HasUnrollLoopControl()); BasicBlock* condition = loop.FindConditionBlock(); EXPECT_EQ(condition->id(), 25u); Instruction* induction = loop.FindConditionVariable(condition); EXPECT_EQ(induction->result_id(), 35u); LoopUtils loop_utils{context.get(), &loop}; EXPECT_TRUE(loop_utils.CanPerformUnroll()); size_t iterations = 0; EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(), &iterations)); EXPECT_EQ(iterations, 3u); } /* Generated from the following GLSL #version 410 core void main() { float out_array[6]; for (uint i = 0; i < 2; i++) { for (int x = 0; x < 3; ++x) { out_array[x + i*3] = i; } } } */ TEST_F(PassClassTest, UnrollNestedLoops) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %35 "out_array" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 0 %7 = OpTypePointer Function %6 %9 = OpConstant %6 0 %16 = OpConstant %6 2 %17 = OpTypeBool %19 = OpTypeInt 32 1 %20 = OpTypePointer Function %19 %22 = OpConstant %19 0 %29 = OpConstant %19 3 %31 = OpTypeFloat 32 %32 = OpConstant %6 6 %33 = OpTypeArray %31 %32 %34 = OpTypePointer Function %33 %39 = OpConstant %6 3 %44 = OpTypePointer Function %31 %47 = OpConstant %19 1 %4 = OpFunction %2 None %3 %5 = OpLabel %35 = OpVariable %34 Function OpBranch %10 %10 = OpLabel %51 = OpPhi %6 %9 %5 %50 %13 OpLoopMerge %12 %13 Unroll OpBranch %14 %14 = OpLabel %18 = OpULessThan %17 %51 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel OpBranch %23 %23 = OpLabel %54 = OpPhi %19 %22 %11 %48 %26 OpLoopMerge %25 %26 Unroll OpBranch %27 %27 = OpLabel %30 = OpSLessThan %17 %54 %29 OpBranchConditional %30 %24 %25 %24 = OpLabel %37 = OpBitcast %6 %54 %40 = OpIMul %6 %51 %39 %41 = OpIAdd %6 %37 %40 %43 = OpConvertUToF %31 %51 %45 = OpAccessChain %44 %35 %41 OpStore %45 %43 OpBranch %26 %26 = OpLabel %48 = OpIAdd %19 %54 %47 OpBranch %23 %25 = OpLabel OpBranch %13 %13 = OpLabel %50 = OpIAdd %6 %51 %47 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "out_array" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 0 %7 = OpTypePointer Function %6 %8 = OpConstant %6 0 %9 = OpConstant %6 2 %10 = OpTypeBool %11 = OpTypeInt 32 1 %12 = OpTypePointer Function %11 %13 = OpConstant %11 0 %14 = OpConstant %11 3 %15 = OpTypeFloat 32 %16 = OpConstant %6 6 %17 = OpTypeArray %15 %16 %18 = OpTypePointer Function %17 %19 = OpConstant %6 3 %20 = OpTypePointer Function %15 %21 = OpConstant %11 1 %2 = OpFunction %4 None %5 %22 = OpLabel %3 = OpVariable %18 Function OpBranch %23 %23 = OpLabel OpBranch %28 %28 = OpLabel %29 = OpULessThan %10 %8 %9 OpBranch %30 %30 = OpLabel OpBranch %31 %31 = OpLabel OpBranch %36 %36 = OpLabel %37 = OpSLessThan %10 %13 %14 OpBranch %38 %38 = OpLabel %39 = OpBitcast %6 %13 %40 = OpIMul %6 %8 %19 %41 = OpIAdd %6 %39 %40 %42 = OpConvertUToF %15 %8 %43 = OpAccessChain %20 %3 %41 OpStore %43 %42 OpBranch %34 %34 = OpLabel %33 = OpIAdd %11 %13 %21 OpBranch %44 %44 = OpLabel OpBranch %46 %46 = OpLabel %47 = OpSLessThan %10 %33 %14 OpBranch %48 %48 = OpLabel %49 = OpBitcast %6 %33 %50 = OpIMul %6 %8 %19 %51 = OpIAdd %6 %49 %50 %52 = OpConvertUToF %15 %8 %53 = OpAccessChain %20 %3 %51 OpStore %53 %52 OpBranch %54 %54 = OpLabel %55 = OpIAdd %11 %33 %21 OpBranch %56 %56 = OpLabel OpBranch %58 %58 = OpLabel %59 = OpSLessThan %10 %55 %14 OpBranch %60 %60 = OpLabel %61 = OpBitcast %6 %55 %62 = OpIMul %6 %8 %19 %63 = OpIAdd %6 %61 %62 %64 = OpConvertUToF %15 %8 %65 = OpAccessChain %20 %3 %63 OpStore %65 %64 OpBranch %66 %66 = OpLabel %67 = OpIAdd %11 %55 %21 OpBranch %35 %35 = OpLabel OpBranch %26 %26 = OpLabel %25 = OpIAdd %6 %8 %21 OpBranch %68 %68 = OpLabel OpBranch %70 %70 = OpLabel %71 = OpULessThan %10 %25 %9 OpBranch %72 %72 = OpLabel OpBranch %73 %73 = OpLabel OpBranch %74 %74 = OpLabel %75 = OpSLessThan %10 %13 %14 OpBranch %76 %76 = OpLabel %77 = OpBitcast %6 %13 %78 = OpIMul %6 %25 %19 %79 = OpIAdd %6 %77 %78 %80 = OpConvertUToF %15 %25 %81 = OpAccessChain %20 %3 %79 OpStore %81 %80 OpBranch %82 %82 = OpLabel %83 = OpIAdd %11 %13 %21 OpBranch %84 %84 = OpLabel OpBranch %85 %85 = OpLabel %86 = OpSLessThan %10 %83 %14 OpBranch %87 %87 = OpLabel %88 = OpBitcast %6 %83 %89 = OpIMul %6 %25 %19 %90 = OpIAdd %6 %88 %89 %91 = OpConvertUToF %15 %25 %92 = OpAccessChain %20 %3 %90 OpStore %92 %91 OpBranch %93 %93 = OpLabel %94 = OpIAdd %11 %83 %21 OpBranch %95 %95 = OpLabel OpBranch %96 %96 = OpLabel %97 = OpSLessThan %10 %94 %14 OpBranch %98 %98 = OpLabel %99 = OpBitcast %6 %94 %100 = OpIMul %6 %25 %19 %101 = OpIAdd %6 %99 %100 %102 = OpConvertUToF %15 %25 %103 = OpAccessChain %20 %3 %101 OpStore %103 %102 OpBranch %104 %104 = OpLabel %105 = OpIAdd %11 %94 %21 OpBranch %106 %106 = OpLabel OpBranch %107 %107 = OpLabel %108 = OpIAdd %6 %25 %21 OpBranch %27 %27 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(text, output, false); } /* Generated from the following GLSL #version 410 core void main() { float out_array[2]; for (int i = -3; i < -1; i++) { out_array[3 + i] = i; } } */ TEST_F(PassClassTest, NegativeConditionAndInit) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %23 "out_array" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 -3 %16 = OpConstant %6 -1 %17 = OpTypeBool %19 = OpTypeInt 32 0 %20 = OpConstant %19 2 %21 = OpTypeArray %6 %20 %22 = OpTypePointer Function %21 %25 = OpConstant %6 3 %30 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel %23 = OpVariable %22 Function OpBranch %10 %10 = OpLabel %32 = OpPhi %6 %9 %5 %31 %13 OpLoopMerge %12 %13 Unroll OpBranch %14 %14 = OpLabel %18 = OpSLessThan %17 %32 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel %26 = OpIAdd %6 %32 %25 %28 = OpAccessChain %7 %23 %26 OpStore %28 %32 OpBranch %13 %13 = OpLabel %31 = OpIAdd %6 %32 %30 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const std::string expected = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "out_array" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %8 = OpConstant %6 -3 %9 = OpConstant %6 -1 %10 = OpTypeBool %11 = OpTypeInt 32 0 %12 = OpConstant %11 2 %13 = OpTypeArray %6 %12 %14 = OpTypePointer Function %13 %15 = OpConstant %6 3 %16 = OpConstant %6 1 %2 = OpFunction %4 None %5 %17 = OpLabel %3 = OpVariable %14 Function OpBranch %18 %18 = OpLabel OpBranch %23 %23 = OpLabel %24 = OpSLessThan %10 %8 %9 OpBranch %25 %25 = OpLabel %26 = OpIAdd %6 %8 %15 %27 = OpAccessChain %7 %3 %26 OpStore %27 %8 OpBranch %21 %21 = OpLabel %20 = OpIAdd %6 %8 %16 OpBranch %28 %28 = OpLabel OpBranch %30 %30 = OpLabel %31 = OpSLessThan %10 %20 %9 OpBranch %32 %32 = OpLabel %33 = OpIAdd %6 %20 %15 %34 = OpAccessChain %7 %3 %33 OpStore %34 %20 OpBranch %35 %35 = OpLabel %36 = OpIAdd %6 %20 %16 OpBranch %22 %22 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); // SinglePassRunAndCheck(text, expected, false); Function* f = spvtest::GetFunction(module, 4); LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); EXPECT_EQ(loop_descriptor.NumLoops(), 1u); Loop& loop = loop_descriptor.GetLoopByIndex(0); EXPECT_TRUE(loop.HasUnrollLoopControl()); BasicBlock* condition = loop.FindConditionBlock(); EXPECT_EQ(condition->id(), 14u); Instruction* induction = loop.FindConditionVariable(condition); EXPECT_EQ(induction->result_id(), 32u); LoopUtils loop_utils{context.get(), &loop}; EXPECT_TRUE(loop_utils.CanPerformUnroll()); size_t iterations = 0; EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(), &iterations)); EXPECT_EQ(iterations, 2u); SinglePassRunAndCheck(text, expected, false); } /* Generated from the following GLSL #version 410 core void main() { float out_array[9]; for (int i = -10; i < -1; i++) { out_array[i] = i; } } */ TEST_F(PassClassTest, NegativeConditionAndInitResidualUnroll) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %23 "out_array" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 -10 %16 = OpConstant %6 -1 %17 = OpTypeBool %19 = OpTypeInt 32 0 %20 = OpConstant %19 9 %21 = OpTypeArray %6 %20 %22 = OpTypePointer Function %21 %25 = OpConstant %6 10 %30 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel %23 = OpVariable %22 Function OpBranch %10 %10 = OpLabel %32 = OpPhi %6 %9 %5 %31 %13 OpLoopMerge %12 %13 Unroll OpBranch %14 %14 = OpLabel %18 = OpSLessThan %17 %32 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel %26 = OpIAdd %6 %32 %25 %28 = OpAccessChain %7 %23 %26 OpStore %28 %32 OpBranch %13 %13 = OpLabel %31 = OpIAdd %6 %32 %30 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const std::string expected = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "out_array" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %8 = OpConstant %6 -10 %9 = OpConstant %6 -1 %10 = OpTypeBool %11 = OpTypeInt 32 0 %12 = OpConstant %11 9 %13 = OpTypeArray %6 %12 %14 = OpTypePointer Function %13 %15 = OpConstant %6 10 %16 = OpConstant %6 1 %48 = OpConstant %6 -9 %2 = OpFunction %4 None %5 %17 = OpLabel %3 = OpVariable %14 Function OpBranch %18 %18 = OpLabel %19 = OpPhi %6 %8 %17 %20 %21 OpLoopMerge %28 %21 Unroll OpBranch %23 %23 = OpLabel %24 = OpSLessThan %10 %19 %48 OpBranchConditional %24 %25 %28 %25 = OpLabel %26 = OpIAdd %6 %19 %15 %27 = OpAccessChain %7 %3 %26 OpStore %27 %19 OpBranch %21 %21 = OpLabel %20 = OpIAdd %6 %19 %16 OpBranch %18 %28 = OpLabel OpBranch %29 %29 = OpLabel %30 = OpPhi %6 %19 %28 %47 %46 OpLoopMerge %38 %46 DontUnroll OpBranch %31 %31 = OpLabel %32 = OpSLessThan %10 %30 %9 OpBranchConditional %32 %33 %38 %33 = OpLabel %34 = OpIAdd %6 %30 %15 %35 = OpAccessChain %7 %3 %34 OpStore %35 %30 OpBranch %36 %36 = OpLabel %37 = OpIAdd %6 %30 %16 OpBranch %39 %39 = OpLabel OpBranch %41 %41 = OpLabel %42 = OpSLessThan %10 %37 %9 OpBranch %43 %43 = OpLabel %44 = OpIAdd %6 %37 %15 %45 = OpAccessChain %7 %3 %44 OpStore %45 %37 OpBranch %46 %46 = OpLabel %47 = OpIAdd %6 %37 %16 OpBranch %29 %38 = OpLabel OpReturn %22 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); Function* f = spvtest::GetFunction(module, 4); LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); EXPECT_EQ(loop_descriptor.NumLoops(), 1u); Loop& loop = loop_descriptor.GetLoopByIndex(0); EXPECT_TRUE(loop.HasUnrollLoopControl()); BasicBlock* condition = loop.FindConditionBlock(); EXPECT_EQ(condition->id(), 14u); Instruction* induction = loop.FindConditionVariable(condition); EXPECT_EQ(induction->result_id(), 32u); LoopUtils loop_utils{context.get(), &loop}; EXPECT_TRUE(loop_utils.CanPerformUnroll()); size_t iterations = 0; EXPECT_TRUE(loop.FindNumberOfIterations(induction, &*condition->ctail(), &iterations)); EXPECT_EQ(iterations, 9u); SinglePassRunAndCheck>(text, expected, false); } /* Generated from the following GLSL #version 410 core void main() { float out_array[10]; for (uint i = 0; i < 2; i++) { for (int x = 0; x < 5; ++x) { out_array[x + i*5] = i; } } } */ TEST_F(PassClassTest, UnrollNestedLoopsValidateDescriptor) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %35 "out_array" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 0 %7 = OpTypePointer Function %6 %9 = OpConstant %6 0 %16 = OpConstant %6 2 %17 = OpTypeBool %19 = OpTypeInt 32 1 %20 = OpTypePointer Function %19 %22 = OpConstant %19 0 %29 = OpConstant %19 5 %31 = OpTypeFloat 32 %32 = OpConstant %6 10 %33 = OpTypeArray %31 %32 %34 = OpTypePointer Function %33 %39 = OpConstant %6 5 %44 = OpTypePointer Function %31 %47 = OpConstant %19 1 %4 = OpFunction %2 None %3 %5 = OpLabel %35 = OpVariable %34 Function OpBranch %10 %10 = OpLabel %51 = OpPhi %6 %9 %5 %50 %13 OpLoopMerge %12 %13 Unroll OpBranch %14 %14 = OpLabel %18 = OpULessThan %17 %51 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel OpBranch %23 %23 = OpLabel %54 = OpPhi %19 %22 %11 %48 %26 OpLoopMerge %25 %26 Unroll OpBranch %27 %27 = OpLabel %30 = OpSLessThan %17 %54 %29 OpBranchConditional %30 %24 %25 %24 = OpLabel %37 = OpBitcast %6 %54 %40 = OpIMul %6 %51 %39 %41 = OpIAdd %6 %37 %40 %43 = OpConvertUToF %31 %51 %45 = OpAccessChain %44 %35 %41 OpStore %45 %43 OpBranch %26 %26 = OpLabel %48 = OpIAdd %19 %54 %47 OpBranch %23 %25 = OpLabel OpBranch %13 %13 = OpLabel %50 = OpIAdd %6 %51 %47 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; { // Test fully unroll std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); Function* f = spvtest::GetFunction(module, 4); LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); EXPECT_EQ(loop_descriptor.NumLoops(), 2u); Loop& outer_loop = loop_descriptor.GetLoopByIndex(1); EXPECT_TRUE(outer_loop.HasUnrollLoopControl()); Loop& inner_loop = loop_descriptor.GetLoopByIndex(0); EXPECT_TRUE(inner_loop.HasUnrollLoopControl()); EXPECT_EQ(outer_loop.GetBlocks().size(), 9u); EXPECT_EQ(inner_loop.GetBlocks().size(), 4u); EXPECT_EQ(outer_loop.NumImmediateChildren(), 1u); EXPECT_EQ(inner_loop.NumImmediateChildren(), 0u); { LoopUtils loop_utils{context.get(), &inner_loop}; loop_utils.FullyUnroll(); loop_utils.Finalize(); } EXPECT_EQ(loop_descriptor.NumLoops(), 1u); EXPECT_EQ(outer_loop.GetBlocks().size(), 25u); EXPECT_EQ(outer_loop.NumImmediateChildren(), 0u); { LoopUtils loop_utils{context.get(), &outer_loop}; loop_utils.FullyUnroll(); loop_utils.Finalize(); } EXPECT_EQ(loop_descriptor.NumLoops(), 0u); } { // Test partially unroll std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); Function* f = spvtest::GetFunction(module, 4); LoopDescriptor& loop_descriptor = *context->GetLoopDescriptor(f); EXPECT_EQ(loop_descriptor.NumLoops(), 2u); Loop& outer_loop = loop_descriptor.GetLoopByIndex(1); EXPECT_TRUE(outer_loop.HasUnrollLoopControl()); Loop& inner_loop = loop_descriptor.GetLoopByIndex(0); EXPECT_TRUE(inner_loop.HasUnrollLoopControl()); EXPECT_EQ(outer_loop.GetBlocks().size(), 9u); EXPECT_EQ(inner_loop.GetBlocks().size(), 4u); EXPECT_EQ(outer_loop.NumImmediateChildren(), 1u); EXPECT_EQ(inner_loop.NumImmediateChildren(), 0u); LoopUtils loop_utils{context.get(), &inner_loop}; loop_utils.PartiallyUnroll(2); loop_utils.Finalize(); // The number of loops should actually grow. EXPECT_EQ(loop_descriptor.NumLoops(), 3u); EXPECT_EQ(outer_loop.GetBlocks().size(), 18u); EXPECT_EQ(outer_loop.NumImmediateChildren(), 2u); } } /* Generated from the following GLSL #version 410 core void main() { float out_array[3]; for (int i = 3; i > 0; --i) { out_array[i] = i; } } */ TEST_F(PassClassTest, FullyUnrollNegativeStepLoopTest) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %24 "out_array" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 3 %16 = OpConstant %6 0 %17 = OpTypeBool %19 = OpTypeFloat 32 %20 = OpTypeInt 32 0 %21 = OpConstant %20 3 %22 = OpTypeArray %19 %21 %23 = OpTypePointer Function %22 %28 = OpTypePointer Function %19 %31 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel %24 = OpVariable %23 Function OpBranch %10 %10 = OpLabel %33 = OpPhi %6 %9 %5 %32 %13 OpLoopMerge %12 %13 Unroll OpBranch %14 %14 = OpLabel %18 = OpSGreaterThan %17 %33 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel %27 = OpConvertSToF %19 %33 %29 = OpAccessChain %28 %24 %33 OpStore %29 %27 OpBranch %13 %13 = OpLabel %32 = OpISub %6 %33 %31 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "out_array" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %8 = OpConstant %6 3 %9 = OpConstant %6 0 %10 = OpTypeBool %11 = OpTypeFloat 32 %12 = OpTypeInt 32 0 %13 = OpConstant %12 3 %14 = OpTypeArray %11 %13 %15 = OpTypePointer Function %14 %16 = OpTypePointer Function %11 %17 = OpConstant %6 1 %2 = OpFunction %4 None %5 %18 = OpLabel %3 = OpVariable %15 Function OpBranch %19 %19 = OpLabel OpBranch %24 %24 = OpLabel %25 = OpSGreaterThan %10 %8 %9 OpBranch %26 %26 = OpLabel %27 = OpConvertSToF %11 %8 %28 = OpAccessChain %16 %3 %8 OpStore %28 %27 OpBranch %22 %22 = OpLabel %21 = OpISub %6 %8 %17 OpBranch %29 %29 = OpLabel OpBranch %31 %31 = OpLabel %32 = OpSGreaterThan %10 %21 %9 OpBranch %33 %33 = OpLabel %34 = OpConvertSToF %11 %21 %35 = OpAccessChain %16 %3 %21 OpStore %35 %34 OpBranch %36 %36 = OpLabel %37 = OpISub %6 %21 %17 OpBranch %38 %38 = OpLabel OpBranch %40 %40 = OpLabel %41 = OpSGreaterThan %10 %37 %9 OpBranch %42 %42 = OpLabel %43 = OpConvertSToF %11 %37 %44 = OpAccessChain %16 %3 %37 OpStore %44 %43 OpBranch %45 %45 = OpLabel %46 = OpISub %6 %37 %17 OpBranch %23 %23 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(text, output, false); } /* Generated from the following GLSL #version 410 core void main() { float out_array[3]; for (int i = 9; i > 0; i-=3) { out_array[i] = i; } } */ TEST_F(PassClassTest, FullyUnrollNegativeNonOneStepLoop) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %24 "out_array" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 9 %16 = OpConstant %6 0 %17 = OpTypeBool %19 = OpTypeFloat 32 %20 = OpTypeInt 32 0 %21 = OpConstant %20 3 %22 = OpTypeArray %19 %21 %23 = OpTypePointer Function %22 %28 = OpTypePointer Function %19 %30 = OpConstant %6 3 %4 = OpFunction %2 None %3 %5 = OpLabel %24 = OpVariable %23 Function OpBranch %10 %10 = OpLabel %33 = OpPhi %6 %9 %5 %32 %13 OpLoopMerge %12 %13 Unroll OpBranch %14 %14 = OpLabel %18 = OpSGreaterThan %17 %33 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel %27 = OpConvertSToF %19 %33 %29 = OpAccessChain %28 %24 %33 OpStore %29 %27 OpBranch %13 %13 = OpLabel %32 = OpISub %6 %33 %30 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "out_array" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %8 = OpConstant %6 9 %9 = OpConstant %6 0 %10 = OpTypeBool %11 = OpTypeFloat 32 %12 = OpTypeInt 32 0 %13 = OpConstant %12 3 %14 = OpTypeArray %11 %13 %15 = OpTypePointer Function %14 %16 = OpTypePointer Function %11 %17 = OpConstant %6 3 %2 = OpFunction %4 None %5 %18 = OpLabel %3 = OpVariable %15 Function OpBranch %19 %19 = OpLabel OpBranch %24 %24 = OpLabel %25 = OpSGreaterThan %10 %8 %9 OpBranch %26 %26 = OpLabel %27 = OpConvertSToF %11 %8 %28 = OpAccessChain %16 %3 %8 OpStore %28 %27 OpBranch %22 %22 = OpLabel %21 = OpISub %6 %8 %17 OpBranch %29 %29 = OpLabel OpBranch %31 %31 = OpLabel %32 = OpSGreaterThan %10 %21 %9 OpBranch %33 %33 = OpLabel %34 = OpConvertSToF %11 %21 %35 = OpAccessChain %16 %3 %21 OpStore %35 %34 OpBranch %36 %36 = OpLabel %37 = OpISub %6 %21 %17 OpBranch %38 %38 = OpLabel OpBranch %40 %40 = OpLabel %41 = OpSGreaterThan %10 %37 %9 OpBranch %42 %42 = OpLabel %43 = OpConvertSToF %11 %37 %44 = OpAccessChain %16 %3 %37 OpStore %44 %43 OpBranch %45 %45 = OpLabel %46 = OpISub %6 %37 %17 OpBranch %23 %23 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(text, output, false); } /* Generated from the following GLSL #version 410 core void main() { float out_array[3]; for (int i = 0; i < 7; i+=3) { out_array[i] = i; } } */ TEST_F(PassClassTest, FullyUnrollNonDivisibleStepLoop) { // With LocalMultiStoreElimPass const std::string text = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %24 "out_array" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 0 %16 = OpConstant %6 7 %17 = OpTypeBool %19 = OpTypeFloat 32 %20 = OpTypeInt 32 0 %21 = OpConstant %20 3 %22 = OpTypeArray %19 %21 %23 = OpTypePointer Function %22 %28 = OpTypePointer Function %19 %30 = OpConstant %6 3 %4 = OpFunction %2 None %3 %5 = OpLabel %24 = OpVariable %23 Function OpBranch %10 %10 = OpLabel %33 = OpPhi %6 %9 %5 %32 %13 OpLoopMerge %12 %13 Unroll OpBranch %14 %14 = OpLabel %18 = OpSLessThan %17 %33 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel %27 = OpConvertSToF %19 %33 %29 = OpAccessChain %28 %24 %33 OpStore %29 %27 OpBranch %13 %13 = OpLabel %32 = OpIAdd %6 %33 %30 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "out_array" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %8 = OpConstant %6 0 %9 = OpConstant %6 7 %10 = OpTypeBool %11 = OpTypeFloat 32 %12 = OpTypeInt 32 0 %13 = OpConstant %12 3 %14 = OpTypeArray %11 %13 %15 = OpTypePointer Function %14 %16 = OpTypePointer Function %11 %17 = OpConstant %6 3 %2 = OpFunction %4 None %5 %18 = OpLabel %3 = OpVariable %15 Function OpBranch %19 %19 = OpLabel OpBranch %24 %24 = OpLabel %25 = OpSLessThan %10 %8 %9 OpBranch %26 %26 = OpLabel %27 = OpConvertSToF %11 %8 %28 = OpAccessChain %16 %3 %8 OpStore %28 %27 OpBranch %22 %22 = OpLabel %21 = OpIAdd %6 %8 %17 OpBranch %29 %29 = OpLabel OpBranch %31 %31 = OpLabel %32 = OpSLessThan %10 %21 %9 OpBranch %33 %33 = OpLabel %34 = OpConvertSToF %11 %21 %35 = OpAccessChain %16 %3 %21 OpStore %35 %34 OpBranch %36 %36 = OpLabel %37 = OpIAdd %6 %21 %17 OpBranch %38 %38 = OpLabel OpBranch %40 %40 = OpLabel %41 = OpSLessThan %10 %37 %9 OpBranch %42 %42 = OpLabel %43 = OpConvertSToF %11 %37 %44 = OpAccessChain %16 %3 %37 OpStore %44 %43 OpBranch %45 %45 = OpLabel %46 = OpIAdd %6 %37 %17 OpBranch %23 %23 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(text, output, false); } /* Generated from the following GLSL #version 410 core void main() { float out_array[4]; for (int i = 11; i > 0; i-=3) { out_array[i] = i; } } */ TEST_F(PassClassTest, FullyUnrollNegativeNonDivisibleStepLoop) { // With LocalMultiStoreElimPass const std::string text = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %24 "out_array" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 11 %16 = OpConstant %6 0 %17 = OpTypeBool %19 = OpTypeFloat 32 %20 = OpTypeInt 32 0 %21 = OpConstant %20 4 %22 = OpTypeArray %19 %21 %23 = OpTypePointer Function %22 %28 = OpTypePointer Function %19 %30 = OpConstant %6 3 %4 = OpFunction %2 None %3 %5 = OpLabel %24 = OpVariable %23 Function OpBranch %10 %10 = OpLabel %33 = OpPhi %6 %9 %5 %32 %13 OpLoopMerge %12 %13 Unroll OpBranch %14 %14 = OpLabel %18 = OpSGreaterThan %17 %33 %16 OpBranchConditional %18 %11 %12 %11 = OpLabel %27 = OpConvertSToF %19 %33 %29 = OpAccessChain %28 %24 %33 OpStore %29 %27 OpBranch %13 %13 = OpLabel %32 = OpISub %6 %33 %30 OpBranch %10 %12 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "out_array" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %8 = OpConstant %6 11 %9 = OpConstant %6 0 %10 = OpTypeBool %11 = OpTypeFloat 32 %12 = OpTypeInt 32 0 %13 = OpConstant %12 4 %14 = OpTypeArray %11 %13 %15 = OpTypePointer Function %14 %16 = OpTypePointer Function %11 %17 = OpConstant %6 3 %2 = OpFunction %4 None %5 %18 = OpLabel %3 = OpVariable %15 Function OpBranch %19 %19 = OpLabel OpBranch %24 %24 = OpLabel %25 = OpSGreaterThan %10 %8 %9 OpBranch %26 %26 = OpLabel %27 = OpConvertSToF %11 %8 %28 = OpAccessChain %16 %3 %8 OpStore %28 %27 OpBranch %22 %22 = OpLabel %21 = OpISub %6 %8 %17 OpBranch %29 %29 = OpLabel OpBranch %31 %31 = OpLabel %32 = OpSGreaterThan %10 %21 %9 OpBranch %33 %33 = OpLabel %34 = OpConvertSToF %11 %21 %35 = OpAccessChain %16 %3 %21 OpStore %35 %34 OpBranch %36 %36 = OpLabel %37 = OpISub %6 %21 %17 OpBranch %38 %38 = OpLabel OpBranch %40 %40 = OpLabel %41 = OpSGreaterThan %10 %37 %9 OpBranch %42 %42 = OpLabel %43 = OpConvertSToF %11 %37 %44 = OpAccessChain %16 %3 %37 OpStore %44 %43 OpBranch %45 %45 = OpLabel %46 = OpISub %6 %37 %17 OpBranch %47 %47 = OpLabel OpBranch %49 %49 = OpLabel %50 = OpSGreaterThan %10 %46 %9 OpBranch %51 %51 = OpLabel %52 = OpConvertSToF %11 %46 %53 = OpAccessChain %16 %3 %46 OpStore %53 %52 OpBranch %54 %54 = OpLabel %55 = OpISub %6 %46 %17 OpBranch %23 %23 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(text, output, false); } // With LocalMultiStoreElimPass static const std::string multiple_phi_shader = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 410 OpName %4 "main" OpName %8 "foo(" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypeFunction %6 %10 = OpTypePointer Function %6 %12 = OpConstant %6 0 %14 = OpConstant %6 3 %22 = OpConstant %6 6 %23 = OpTypeBool %31 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel %40 = OpFunctionCall %6 %8 OpReturn OpFunctionEnd %8 = OpFunction %6 None %7 %9 = OpLabel OpBranch %16 %16 = OpLabel %41 = OpPhi %6 %12 %9 %34 %19 %42 = OpPhi %6 %14 %9 %29 %19 %43 = OpPhi %6 %12 %9 %32 %19 OpLoopMerge %18 %19 Unroll OpBranch %20 %20 = OpLabel %24 = OpSLessThan %23 %43 %22 OpBranchConditional %24 %17 %18 %17 = OpLabel %27 = OpIMul %6 %43 %41 %29 = OpIAdd %6 %42 %27 OpBranch %19 %19 = OpLabel %32 = OpIAdd %6 %43 %31 %34 = OpISub %6 %41 %31 OpBranch %16 %18 = OpLabel %37 = OpIAdd %6 %42 %41 OpReturnValue %37 OpFunctionEnd )"; TEST_F(PassClassTest, PartiallyUnrollResidualMultipleInductionVariables) { const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "foo(" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypeFunction %6 %8 = OpTypePointer Function %6 %9 = OpConstant %6 0 %10 = OpConstant %6 3 %11 = OpConstant %6 6 %12 = OpTypeBool %13 = OpConstant %6 1 %82 = OpTypeInt 32 0 %83 = OpConstant %82 2 %2 = OpFunction %4 None %5 %14 = OpLabel %15 = OpFunctionCall %6 %3 OpReturn OpFunctionEnd %3 = OpFunction %6 None %7 %16 = OpLabel OpBranch %17 %17 = OpLabel %18 = OpPhi %6 %9 %16 %19 %20 %21 = OpPhi %6 %10 %16 %22 %20 %23 = OpPhi %6 %9 %16 %24 %20 OpLoopMerge %31 %20 Unroll OpBranch %26 %26 = OpLabel %27 = OpSLessThan %12 %23 %83 OpBranchConditional %27 %28 %31 %28 = OpLabel %29 = OpIMul %6 %23 %18 %22 = OpIAdd %6 %21 %29 OpBranch %20 %20 = OpLabel %24 = OpIAdd %6 %23 %13 %19 = OpISub %6 %18 %13 OpBranch %17 %31 = OpLabel OpBranch %32 %32 = OpLabel %33 = OpPhi %6 %18 %31 %81 %79 %34 = OpPhi %6 %21 %31 %78 %79 %35 = OpPhi %6 %23 %31 %80 %79 OpLoopMerge %44 %79 DontUnroll OpBranch %36 %36 = OpLabel %37 = OpSLessThan %12 %35 %11 OpBranchConditional %37 %38 %44 %38 = OpLabel %39 = OpIMul %6 %35 %33 %40 = OpIAdd %6 %34 %39 OpBranch %41 %41 = OpLabel %42 = OpIAdd %6 %35 %13 %43 = OpISub %6 %33 %13 OpBranch %46 %46 = OpLabel OpBranch %50 %50 = OpLabel %51 = OpSLessThan %12 %42 %11 OpBranch %52 %52 = OpLabel %53 = OpIMul %6 %42 %43 %54 = OpIAdd %6 %40 %53 OpBranch %55 %55 = OpLabel %56 = OpIAdd %6 %42 %13 %57 = OpISub %6 %43 %13 OpBranch %58 %58 = OpLabel OpBranch %62 %62 = OpLabel %63 = OpSLessThan %12 %56 %11 OpBranch %64 %64 = OpLabel %65 = OpIMul %6 %56 %57 %66 = OpIAdd %6 %54 %65 OpBranch %67 %67 = OpLabel %68 = OpIAdd %6 %56 %13 %69 = OpISub %6 %57 %13 OpBranch %70 %70 = OpLabel OpBranch %74 %74 = OpLabel %75 = OpSLessThan %12 %68 %11 OpBranch %76 %76 = OpLabel %77 = OpIMul %6 %68 %69 %78 = OpIAdd %6 %66 %77 OpBranch %79 %79 = OpLabel %80 = OpIAdd %6 %68 %13 %81 = OpISub %6 %69 %13 OpBranch %32 %44 = OpLabel %45 = OpIAdd %6 %34 %33 OpReturnValue %45 %25 = OpLabel %30 = OpIAdd %6 %34 %33 OpReturnValue %30 OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << multiple_phi_shader << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck>(multiple_phi_shader, output, false); } TEST_F(PassClassTest, PartiallyUnrollMultipleInductionVariables) { const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "foo(" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypeFunction %6 %8 = OpTypePointer Function %6 %9 = OpConstant %6 0 %10 = OpConstant %6 3 %11 = OpConstant %6 6 %12 = OpTypeBool %13 = OpConstant %6 1 %2 = OpFunction %4 None %5 %14 = OpLabel %15 = OpFunctionCall %6 %3 OpReturn OpFunctionEnd %3 = OpFunction %6 None %7 %16 = OpLabel OpBranch %17 %17 = OpLabel %18 = OpPhi %6 %9 %16 %42 %40 %21 = OpPhi %6 %10 %16 %39 %40 %23 = OpPhi %6 %9 %16 %41 %40 OpLoopMerge %25 %40 DontUnroll OpBranch %26 %26 = OpLabel %27 = OpSLessThan %12 %23 %11 OpBranchConditional %27 %28 %25 %28 = OpLabel %29 = OpIMul %6 %23 %18 %22 = OpIAdd %6 %21 %29 OpBranch %20 %20 = OpLabel %24 = OpIAdd %6 %23 %13 %19 = OpISub %6 %18 %13 OpBranch %31 %31 = OpLabel OpBranch %35 %35 = OpLabel %36 = OpSLessThan %12 %24 %11 OpBranch %37 %37 = OpLabel %38 = OpIMul %6 %24 %19 %39 = OpIAdd %6 %22 %38 OpBranch %40 %40 = OpLabel %41 = OpIAdd %6 %24 %13 %42 = OpISub %6 %19 %13 OpBranch %17 %25 = OpLabel %30 = OpIAdd %6 %21 %18 OpReturnValue %30 OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << multiple_phi_shader << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck>(multiple_phi_shader, output, false); } TEST_F(PassClassTest, FullyUnrollMultipleInductionVariables) { const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 410 OpName %2 "main" OpName %3 "foo(" %4 = OpTypeVoid %5 = OpTypeFunction %4 %6 = OpTypeInt 32 1 %7 = OpTypeFunction %6 %8 = OpTypePointer Function %6 %9 = OpConstant %6 0 %10 = OpConstant %6 3 %11 = OpConstant %6 6 %12 = OpTypeBool %13 = OpConstant %6 1 %2 = OpFunction %4 None %5 %14 = OpLabel %15 = OpFunctionCall %6 %3 OpReturn OpFunctionEnd %3 = OpFunction %6 None %7 %16 = OpLabel OpBranch %17 %17 = OpLabel OpBranch %26 %26 = OpLabel %27 = OpSLessThan %12 %9 %11 OpBranch %28 %28 = OpLabel %29 = OpIMul %6 %9 %9 %22 = OpIAdd %6 %10 %29 OpBranch %20 %20 = OpLabel %24 = OpIAdd %6 %9 %13 %19 = OpISub %6 %9 %13 OpBranch %31 %31 = OpLabel OpBranch %35 %35 = OpLabel %36 = OpSLessThan %12 %24 %11 OpBranch %37 %37 = OpLabel %38 = OpIMul %6 %24 %19 %39 = OpIAdd %6 %22 %38 OpBranch %40 %40 = OpLabel %41 = OpIAdd %6 %24 %13 %42 = OpISub %6 %19 %13 OpBranch %43 %43 = OpLabel OpBranch %47 %47 = OpLabel %48 = OpSLessThan %12 %41 %11 OpBranch %49 %49 = OpLabel %50 = OpIMul %6 %41 %42 %51 = OpIAdd %6 %39 %50 OpBranch %52 %52 = OpLabel %53 = OpIAdd %6 %41 %13 %54 = OpISub %6 %42 %13 OpBranch %55 %55 = OpLabel OpBranch %59 %59 = OpLabel %60 = OpSLessThan %12 %53 %11 OpBranch %61 %61 = OpLabel %62 = OpIMul %6 %53 %54 %63 = OpIAdd %6 %51 %62 OpBranch %64 %64 = OpLabel %65 = OpIAdd %6 %53 %13 %66 = OpISub %6 %54 %13 OpBranch %67 %67 = OpLabel OpBranch %71 %71 = OpLabel %72 = OpSLessThan %12 %65 %11 OpBranch %73 %73 = OpLabel %74 = OpIMul %6 %65 %66 %75 = OpIAdd %6 %63 %74 OpBranch %76 %76 = OpLabel %77 = OpIAdd %6 %65 %13 %78 = OpISub %6 %66 %13 OpBranch %79 %79 = OpLabel OpBranch %83 %83 = OpLabel %84 = OpSLessThan %12 %77 %11 OpBranch %85 %85 = OpLabel %86 = OpIMul %6 %77 %78 %87 = OpIAdd %6 %75 %86 OpBranch %88 %88 = OpLabel %89 = OpIAdd %6 %77 %13 %90 = OpISub %6 %78 %13 OpBranch %25 %25 = OpLabel %30 = OpIAdd %6 %87 %90 OpReturnValue %30 OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, multiple_phi_shader, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << multiple_phi_shader << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(multiple_phi_shader, output, false); } /* Generated from the following GLSL #version 440 core void main() { int j = 0; for (int i = 0; i <= 2; ++i) ++j; for (int i = 1; i >= 0; --i) ++j; } */ TEST_F(PassClassTest, FullyUnrollEqualToOperations) { // With LocalMultiStoreElimPass const std::string text = R"( OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %4 "main" OpExecutionMode %4 OriginUpperLeft OpSource GLSL 440 OpName %4 "main" %2 = OpTypeVoid %3 = OpTypeFunction %2 %6 = OpTypeInt 32 1 %7 = OpTypePointer Function %6 %9 = OpConstant %6 0 %17 = OpConstant %6 2 %18 = OpTypeBool %21 = OpConstant %6 1 %4 = OpFunction %2 None %3 %5 = OpLabel OpBranch %11 %11 = OpLabel %37 = OpPhi %6 %9 %5 %22 %14 %38 = OpPhi %6 %9 %5 %24 %14 OpLoopMerge %13 %14 Unroll OpBranch %15 %15 = OpLabel %19 = OpSLessThanEqual %18 %38 %17 OpBranchConditional %19 %12 %13 %12 = OpLabel %22 = OpIAdd %6 %37 %21 OpBranch %14 %14 = OpLabel %24 = OpIAdd %6 %38 %21 OpBranch %11 %13 = OpLabel OpBranch %26 %26 = OpLabel %39 = OpPhi %6 %37 %13 %34 %29 %40 = OpPhi %6 %21 %13 %36 %29 OpLoopMerge %28 %29 Unroll OpBranch %30 %30 = OpLabel %32 = OpSGreaterThanEqual %18 %40 %9 OpBranchConditional %32 %27 %28 %27 = OpLabel %34 = OpIAdd %6 %39 %21 OpBranch %29 %29 = OpLabel %36 = OpISub %6 %40 %21 OpBranch %26 %28 = OpLabel OpReturn OpFunctionEnd )"; const std::string output = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 440 OpName %2 "main" %3 = OpTypeVoid %4 = OpTypeFunction %3 %5 = OpTypeInt 32 1 %6 = OpTypePointer Function %5 %7 = OpConstant %5 0 %8 = OpConstant %5 2 %9 = OpTypeBool %10 = OpConstant %5 1 %2 = OpFunction %3 None %4 %11 = OpLabel OpBranch %12 %12 = OpLabel OpBranch %19 %19 = OpLabel %20 = OpSLessThanEqual %9 %7 %8 OpBranch %21 %21 = OpLabel %14 = OpIAdd %5 %7 %10 OpBranch %15 %15 = OpLabel %17 = OpIAdd %5 %7 %10 OpBranch %41 %41 = OpLabel OpBranch %44 %44 = OpLabel %45 = OpSLessThanEqual %9 %17 %8 OpBranch %46 %46 = OpLabel %47 = OpIAdd %5 %14 %10 OpBranch %48 %48 = OpLabel %49 = OpIAdd %5 %17 %10 OpBranch %50 %50 = OpLabel OpBranch %53 %53 = OpLabel %54 = OpSLessThanEqual %9 %49 %8 OpBranch %55 %55 = OpLabel %56 = OpIAdd %5 %47 %10 OpBranch %57 %57 = OpLabel %58 = OpIAdd %5 %49 %10 OpBranch %18 %18 = OpLabel OpBranch %22 %22 = OpLabel OpBranch %29 %29 = OpLabel %30 = OpSGreaterThanEqual %9 %10 %7 OpBranch %31 %31 = OpLabel %24 = OpIAdd %5 %56 %10 OpBranch %25 %25 = OpLabel %27 = OpISub %5 %10 %10 OpBranch %32 %32 = OpLabel OpBranch %35 %35 = OpLabel %36 = OpSGreaterThanEqual %9 %27 %7 OpBranch %37 %37 = OpLabel %38 = OpIAdd %5 %24 %10 OpBranch %39 %39 = OpLabel %40 = OpISub %5 %27 %10 OpBranch %28 %28 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << text << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(text, output, false); } // With LocalMultiStoreElimPass const std::string condition_in_header = R"( OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %main "main" %o OpExecutionMode %main OriginUpperLeft OpSource GLSL 430 OpDecorate %o Location 0 %void = OpTypeVoid %6 = OpTypeFunction %void %int = OpTypeInt 32 1 %int_n2 = OpConstant %int -2 %int_2 = OpConstant %int 2 %bool = OpTypeBool %float = OpTypeFloat 32 %_ptr_Output_float = OpTypePointer Output %float %o = OpVariable %_ptr_Output_float Output %float_1 = OpConstant %float 1 %main = OpFunction %void None %6 %15 = OpLabel OpBranch %16 %16 = OpLabel %27 = OpPhi %int %int_n2 %15 %26 %18 %21 = OpSLessThanEqual %bool %27 %int_2 OpLoopMerge %17 %18 Unroll OpBranchConditional %21 %22 %17 %22 = OpLabel %23 = OpLoad %float %o %24 = OpFAdd %float %23 %float_1 OpStore %o %24 OpBranch %18 %18 = OpLabel %26 = OpIAdd %int %27 %int_2 OpBranch %16 %17 = OpLabel OpReturn OpFunctionEnd )"; TEST_F(PassClassTest, FullyUnrollConditionIsInHeaderBlock) { const std::string output = R"(OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %1 "main" %2 OpExecutionMode %1 OriginUpperLeft OpSource GLSL 430 OpDecorate %2 Location 0 %3 = OpTypeVoid %4 = OpTypeFunction %3 %5 = OpTypeInt 32 1 %6 = OpConstant %5 -2 %7 = OpConstant %5 2 %8 = OpTypeBool %9 = OpTypeFloat 32 %10 = OpTypePointer Output %9 %2 = OpVariable %10 Output %11 = OpConstant %9 1 %1 = OpFunction %3 None %4 %12 = OpLabel OpBranch %13 %13 = OpLabel %17 = OpSLessThanEqual %8 %6 %7 OpBranch %19 %19 = OpLabel %20 = OpLoad %9 %2 %21 = OpFAdd %9 %20 %11 OpStore %2 %21 OpBranch %16 %16 = OpLabel %15 = OpIAdd %5 %6 %7 OpBranch %22 %22 = OpLabel %24 = OpSLessThanEqual %8 %15 %7 OpBranch %25 %25 = OpLabel %26 = OpLoad %9 %2 %27 = OpFAdd %9 %26 %11 OpStore %2 %27 OpBranch %28 %28 = OpLabel %29 = OpIAdd %5 %15 %7 OpBranch %30 %30 = OpLabel %32 = OpSLessThanEqual %8 %29 %7 OpBranch %33 %33 = OpLabel %34 = OpLoad %9 %2 %35 = OpFAdd %9 %34 %11 OpStore %2 %35 OpBranch %36 %36 = OpLabel %37 = OpIAdd %5 %29 %7 OpBranch %18 %18 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, condition_in_header, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << condition_in_header << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck(condition_in_header, output, false); } TEST_F(PassClassTest, PartiallyUnrollResidualConditionIsInHeaderBlock) { const std::string output = R"(OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %1 "main" %2 OpExecutionMode %1 OriginUpperLeft OpSource GLSL 430 OpDecorate %2 Location 0 %3 = OpTypeVoid %4 = OpTypeFunction %3 %5 = OpTypeInt 32 1 %6 = OpConstant %5 -2 %7 = OpConstant %5 2 %8 = OpTypeBool %9 = OpTypeFloat 32 %10 = OpTypePointer Output %9 %2 = OpVariable %10 Output %11 = OpConstant %9 1 %40 = OpTypeInt 32 0 %41 = OpConstant %40 1 %1 = OpFunction %3 None %4 %12 = OpLabel OpBranch %13 %13 = OpLabel %14 = OpPhi %5 %6 %12 %15 %16 %17 = OpSLessThanEqual %8 %14 %41 OpLoopMerge %22 %16 Unroll OpBranchConditional %17 %19 %22 %19 = OpLabel %20 = OpLoad %9 %2 %21 = OpFAdd %9 %20 %11 OpStore %2 %21 OpBranch %16 %16 = OpLabel %15 = OpIAdd %5 %14 %7 OpBranch %13 %22 = OpLabel OpBranch %23 %23 = OpLabel %24 = OpPhi %5 %14 %22 %39 %38 %25 = OpSLessThanEqual %8 %24 %7 OpLoopMerge %31 %38 DontUnroll OpBranchConditional %25 %26 %31 %26 = OpLabel %27 = OpLoad %9 %2 %28 = OpFAdd %9 %27 %11 OpStore %2 %28 OpBranch %29 %29 = OpLabel %30 = OpIAdd %5 %24 %7 OpBranch %32 %32 = OpLabel %34 = OpSLessThanEqual %8 %30 %7 OpBranch %35 %35 = OpLabel %36 = OpLoad %9 %2 %37 = OpFAdd %9 %36 %11 OpStore %2 %37 OpBranch %38 %38 = OpLabel %39 = OpIAdd %5 %30 %7 OpBranch %23 %31 = OpLabel OpReturn %18 = OpLabel OpReturn OpFunctionEnd )"; std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, condition_in_header, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for ushader:\n" << condition_in_header << std::endl; LoopUnroller loop_unroller; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck>(condition_in_header, output, false); } /* Generated from following GLSL with latch block artificially inserted to be seperate from continue. #version 430 void main(void) { float x[10]; for (int i = 0; i < 10; ++i) { x[i] = i; } } */ TEST_F(PassClassTest, PartiallyUnrollLatchNotContinue) { const std::string text = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 430 OpName %2 "main" OpName %3 "i" OpName %4 "x" %5 = OpTypeVoid %6 = OpTypeFunction %5 %7 = OpTypeInt 32 1 %8 = OpTypePointer Function %7 %9 = OpConstant %7 0 %10 = OpConstant %7 10 %11 = OpTypeBool %12 = OpTypeFloat 32 %13 = OpTypeInt 32 0 %14 = OpConstant %13 10 %15 = OpTypeArray %12 %14 %16 = OpTypePointer Function %15 %17 = OpTypePointer Function %12 %18 = OpConstant %7 1 %2 = OpFunction %5 None %6 %19 = OpLabel %3 = OpVariable %8 Function %4 = OpVariable %16 Function OpStore %3 %9 OpBranch %20 %20 = OpLabel %21 = OpPhi %7 %9 %19 %22 %30 OpLoopMerge %24 %23 Unroll OpBranch %25 %25 = OpLabel %26 = OpSLessThan %11 %21 %10 OpBranchConditional %26 %27 %24 %27 = OpLabel %28 = OpConvertSToF %12 %21 %29 = OpAccessChain %17 %4 %21 OpStore %29 %28 OpBranch %23 %23 = OpLabel %22 = OpIAdd %7 %21 %18 OpStore %3 %22 OpBranch %30 %30 = OpLabel OpBranch %20 %24 = OpLabel OpReturn OpFunctionEnd )"; const std::string expected = R"(OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %2 "main" OpExecutionMode %2 OriginUpperLeft OpSource GLSL 430 OpName %2 "main" OpName %3 "i" OpName %4 "x" %5 = OpTypeVoid %6 = OpTypeFunction %5 %7 = OpTypeInt 32 1 %8 = OpTypePointer Function %7 %9 = OpConstant %7 0 %10 = OpConstant %7 10 %11 = OpTypeBool %12 = OpTypeFloat 32 %13 = OpTypeInt 32 0 %14 = OpConstant %13 10 %15 = OpTypeArray %12 %14 %16 = OpTypePointer Function %15 %17 = OpTypePointer Function %12 %18 = OpConstant %7 1 %63 = OpConstant %13 1 %2 = OpFunction %5 None %6 %19 = OpLabel %3 = OpVariable %8 Function %4 = OpVariable %16 Function OpStore %3 %9 OpBranch %20 %20 = OpLabel %21 = OpPhi %7 %9 %19 %22 %23 OpLoopMerge %31 %25 Unroll OpBranch %26 %26 = OpLabel %27 = OpSLessThan %11 %21 %63 OpBranchConditional %27 %28 %31 %28 = OpLabel %29 = OpConvertSToF %12 %21 %30 = OpAccessChain %17 %4 %21 OpStore %30 %29 OpBranch %25 %25 = OpLabel %22 = OpIAdd %7 %21 %18 OpStore %3 %22 OpBranch %23 %23 = OpLabel OpBranch %20 %31 = OpLabel OpBranch %32 %32 = OpLabel %33 = OpPhi %7 %21 %31 %61 %62 OpLoopMerge %42 %60 DontUnroll OpBranch %34 %34 = OpLabel %35 = OpSLessThan %11 %33 %10 OpBranchConditional %35 %36 %42 %36 = OpLabel %37 = OpConvertSToF %12 %33 %38 = OpAccessChain %17 %4 %33 OpStore %38 %37 OpBranch %39 %39 = OpLabel %40 = OpIAdd %7 %33 %18 OpStore %3 %40 OpBranch %41 %41 = OpLabel OpBranch %43 %43 = OpLabel OpBranch %45 %45 = OpLabel %46 = OpSLessThan %11 %40 %10 OpBranch %47 %47 = OpLabel %48 = OpConvertSToF %12 %40 %49 = OpAccessChain %17 %4 %40 OpStore %49 %48 OpBranch %50 %50 = OpLabel %51 = OpIAdd %7 %40 %18 OpStore %3 %51 OpBranch %52 %52 = OpLabel OpBranch %53 %53 = OpLabel OpBranch %55 %55 = OpLabel %56 = OpSLessThan %11 %51 %10 OpBranch %57 %57 = OpLabel %58 = OpConvertSToF %12 %51 %59 = OpAccessChain %17 %4 %51 OpStore %59 %58 OpBranch %60 %60 = OpLabel %61 = OpIAdd %7 %51 %18 OpStore %3 %61 OpBranch %62 %62 = OpLabel OpBranch %32 %42 = OpLabel OpReturn %24 = OpLabel OpReturn OpFunctionEnd )"; SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER); SinglePassRunAndCheck>(text, expected, true); // Make sure the latch block information is preserved and propagated correctly // by the pass. std::unique_ptr context = BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); PartialUnrollerTestPass<3> unroller; unroller.SetContextForTesting(context.get()); unroller.Process(); Module* module = context->module(); EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" << text << std::endl; const Function* f = spvtest::GetFunction(module, 2); LoopDescriptor ld{context.get(), f}; EXPECT_EQ(ld.NumLoops(), 2u); Loop& loop_1 = ld.GetLoopByIndex(0u); EXPECT_NE(loop_1.GetLatchBlock(), loop_1.GetContinueBlock()); Loop& loop_2 = ld.GetLoopByIndex(1u); EXPECT_NE(loop_2.GetLatchBlock(), loop_2.GetContinueBlock()); } // Test that a loop with a self-referencing OpPhi instruction is handled // correctly. TEST_F(PassClassTest, OpPhiSelfReference) { const std::string text = R"( ; Find the two adds from the unrolled loop ; CHECK: OpIAdd ; CHECK: OpIAdd ; CHECK: OpIAdd %uint %uint_0 %uint_1 ; CHECK-NEXT: OpReturn OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %2 "main" OpExecutionMode %2 LocalSize 8 8 1 OpSource HLSL 600 %uint = OpTypeInt 32 0 %void = OpTypeVoid %5 = OpTypeFunction %void %uint_0 = OpConstant %uint 0 %uint_1 = OpConstant %uint 1 %bool = OpTypeBool %true = OpConstantTrue %bool %2 = OpFunction %void None %5 %10 = OpLabel OpBranch %19 %19 = OpLabel %20 = OpPhi %uint %uint_0 %10 %20 %21 %22 = OpPhi %uint %uint_0 %10 %23 %21 %24 = OpULessThanEqual %bool %22 %uint_1 OpLoopMerge %25 %21 Unroll OpBranchConditional %24 %21 %25 %21 = OpLabel %23 = OpIAdd %uint %22 %uint_1 OpBranch %19 %25 = OpLabel %14 = OpIAdd %uint %20 %uint_1 OpReturn OpFunctionEnd )"; const bool kFullyUnroll = true; const uint32_t kUnrollFactor = 0; SinglePassRunAndMatch(text, true, kFullyUnroll, kUnrollFactor); } // Test that a loop containing an unreachable merge block can still be unrolled // correctly. TEST_F(PassClassTest, UnreachableMerge) { const std::string text = R"( ; Identify the first iteration of the unrolled loop, and make sure it contains ; the unreachable merge block. ; The first SelectionMerge corresponds to the original loop merge. ; The second is the branch in the loop. ; CHECK: OpSelectionMerge {{%\w+}} None ; CHECK: OpSelectionMerge [[unrch1:%\w+]] None ; CHECK: [[unrch1]] = OpLabel ; CHECK-NEXT: OpUnreachable ; Identify the second iteration of the unrolled loop, and make sure it contains ; the unreachable merge block. ; The first SelectionMerge corresponds to the original loop merge ; The second is the branch in the loop. ; CHECK: OpSelectionMerge {{%\w+}} None ; CHECK: OpSelectionMerge [[unrch2:%\w+]] None ; CHECK: [[unrch2]] = OpLabel ; CHECK-NEXT: OpUnreachable OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %main "main" OpExecutionMode %main LocalSize 64 1 1 OpSource HLSL 600 OpName %main "main" %uint = OpTypeInt 32 0 %uint_0 = OpConstant %uint 0 %uint_2 = OpConstant %uint 2 %uint_1 = OpConstant %uint 1 %bool = OpTypeBool %void = OpTypeVoid %18 = OpTypeFunction %void %main = OpFunction %void None %18 %23 = OpLabel OpBranch %24 %24 = OpLabel %28 = OpPhi %uint %uint_0 %23 %29 %27 %30 = OpULessThan %bool %28 %uint_2 OpLoopMerge %31 %27 Unroll OpBranchConditional %30 %32 %31 %32 = OpLabel OpSelectionMerge %33 None OpSwitch %uint_0 %34 %34 = OpLabel %35 = OpUndef %bool OpSelectionMerge %36 None OpBranchConditional %35 %37 %38 %38 = OpLabel OpBranch %33 %37 = OpLabel OpBranch %33 %36 = OpLabel OpUnreachable %33 = OpLabel OpBranch %27 %27 = OpLabel %29 = OpIAdd %uint %28 %uint_1 OpBranch %24 %31 = OpLabel OpReturn OpFunctionEnd )"; const bool kFullyUnroll = true; const uint32_t kUnrollFactor = 0; SinglePassRunAndMatch(text, true, kFullyUnroll, kUnrollFactor); } } // namespace } // namespace opt } // namespace spvtools