// Copyright (c) 2025 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 <iostream>
#include <ostream>

#include "spirv-tools/optimizer.hpp"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"

namespace spvtools {
namespace opt {
namespace {

struct ResolveBindingConflictsTest : public PassTest<::testing::Test> {
  virtual void SetUp() override {
    SetTargetEnv(SPV_ENV_VULKAN_1_1);  // allow storage buffer storage class
                                       // without extension.
    SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
    SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
                          SPV_BINARY_TO_TEXT_OPTION_INDENT |
                          SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
  }
};

using StringList = std::vector<std::string>;
std::string EntryPointDecls(const StringList& names) {
  std::ostringstream os;
  for (auto& name : names) {
    os << "               OpEntryPoint GLCompute %" + name + " \"" + name +
              "\"\n";
  }
  for (auto& name : names) {
    os << "               OpExecutionMode %" + name + " LocalSize 1 1 1\n";
  }
  for (auto& name : names) {
    os << "               OpName %" + name + " \"" + name + "\"\n";
  }
  return os.str();
}

std::string Preamble(const StringList& names = {"main"}) {
  return R"(               OpCapability Shader
               OpMemoryModel Logical GLSL450
)" + EntryPointDecls(names) +
         R"(               OpName %voidfn "voidfn"
               OpName %s_ty "s_ty"
               OpName %i_ty "i_ty"
               OpName %si_ty "si_ty"
               OpName %p_s_ty "p_s_ty"
               OpName %p_i_ty "p_i_ty"
               OpName %p_si_ty "p_si_ty"
               OpName %st_ty "st_ty"
               OpName %pu_st_ty "pu_st_ty"
               OpName %pb_st_ty "pb_st_ty"
)";
}

std::string BasicTypes() {
  return R"(               OpDecorate %st_ty Block
               OpMemberDecorate %st_ty 0 Offset 0
      %float = OpTypeFloat 32
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_3 = OpConstant %uint 3
       %void = OpTypeVoid
     %voidfn = OpTypeFunction %void
       %s_ty = OpTypeSampler
       %i_ty = OpTypeImage %float 2D 0 0 0 1 Unknown
      %si_ty = OpTypeSampledImage %i_ty
     %p_i_ty = OpTypePointer UniformConstant %i_ty
     %p_s_ty = OpTypePointer UniformConstant %s_ty
    %p_si_ty = OpTypePointer UniformConstant %si_ty
      %st_ty = OpTypeStruct %uint
   %pu_st_ty = OpTypePointer Uniform %st_ty
   %pb_st_ty = OpTypePointer StorageBuffer %st_ty
)";
}
std::string NoCheck() { return "; CHECK-NOT: nothing to see"; }

TEST_F(ResolveBindingConflictsTest, NoBindings_NoChange) {
  const std::string kTest = Preamble() + BasicTypes() +
                            R"(       %main = OpFunction %void None %voidfn
        %100 = OpLabel
               OpReturn
               OpFunctionEnd
)";
  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest + NoCheck(), /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithoutChange);
  EXPECT_EQ(kTest, disasm);
}

TEST_F(ResolveBindingConflictsTest, NoConflict_UnusedVars_NoChange) {
  const std::string kTest = Preamble() +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 0
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 0
               OpDecorate %104 DescriptorSet 0
               OpDecorate %104 Binding 0

      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 0
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 0
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 0
      ; CHECK: OpDecorate %104 DescriptorSet 0
      ; CHECK: OpDecorate %104 Binding 0

)" + BasicTypes() + R"(

 ; Unused variables

        %100 = OpVariable %p_i_ty UniformConstant  ; image
        %101 = OpVariable %p_s_ty UniformConstant  ; sampler
        %102 = OpVariable %pu_st_ty Uniform        ; UBO
        %103 = OpVariable %pb_st_ty StorageBuffer  ; SSBO
        %104 = OpVariable %p_si_ty UniformConstant ; combined sampled image

       %main = OpFunction %void None %voidfn
         %10 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest, NoConflict_UsedVars_NoChange) {
  const std::string kTest = Preamble() +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 1
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 2
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 3
               OpDecorate %110 DescriptorSet 1
               OpDecorate %110 Binding 0

      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 0
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 1
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 2
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 3
      ; CHECK: OpDecorate %110 DescriptorSet 1
      ; CHECK: OpDecorate %110 Binding 0

)" + BasicTypes() + R"(

        %100 = OpVariable %p_i_ty UniformConstant  ; image
        %101 = OpVariable %p_s_ty UniformConstant  ; sampler
        %102 = OpVariable %pu_st_ty Uniform        ; UBO
        %103 = OpVariable %pb_st_ty StorageBuffer  ; SSBO
        %110 = OpVariable %p_si_ty UniformConstant ; combined sampled image

       %main = OpFunction %void None %voidfn
         %10 = OpLabel
         %11 = OpCopyObject %p_i_ty %100
         %12 = OpCopyObject %p_s_ty %101
         %13 = OpCopyObject %pu_st_ty %102
         %14 = OpCopyObject %pb_st_ty %103
         %15 = OpCopyObject %p_si_ty %110
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithoutChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest,
       OneEntryPoint_SamplerFirstConflict_Resolves) {
  const std::string kTest = Preamble() +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0

      ; The sampler's binding number is incremented, even when listed first.
      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant ; sampler listed first
        %101 = OpVariable %p_i_ty UniformConstant

       %main = OpFunction %void None %voidfn
         %10 = OpLabel
         %11 = OpCopyObject %p_s_ty %100
         %12 = OpCopyObject %p_i_ty %101
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest,
       OneEntryPoint_SamplerSecondConflict_Resolves) {
  const std::string kTest = Preamble() +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0

      ; The sampler's binding number is incremented, even when listed second.
      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 0
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 1

)" + BasicTypes() + R"(

        %100 = OpVariable %p_i_ty UniformConstant
        %101 = OpVariable %p_s_ty UniformConstant ; sampler listed second

       %main = OpFunction %void None %voidfn
         %10 = OpLabel
         %11 = OpCopyObject %p_i_ty %100
         %12 = OpCopyObject %p_s_ty %101
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest, OneEntryPoint_Conflict_Ripples) {
  const std::string kTest = Preamble() +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 1
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 2
               OpDecorate %104 DescriptorSet 0
               OpDecorate %104 Binding 3

      ; The sampler's binding number is incremented, and later
      ; bindings move out of the way.
      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 2
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 3
      ; CHECK: OpDecorate %104 DescriptorSet 0
      ; CHECK: OpDecorate %104 Binding 4

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant ; sampler comes first
        %101 = OpVariable %p_i_ty UniformConstant
        %102 = OpVariable %pu_st_ty Uniform
        %103 = OpVariable %pb_st_ty StorageBuffer
        %104 = OpVariable %p_si_ty UniformConstant

       %main = OpFunction %void None %voidfn
         %10 = OpLabel
         %11 = OpCopyObject %p_s_ty %100
         %12 = OpCopyObject %p_i_ty %101
         %13 = OpCopyObject %pu_st_ty %102
         %14 = OpCopyObject %pb_st_ty %103
         %15 = OpCopyObject %p_si_ty %104
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest,
       OneEntryPoint_Conflict_RippleStopsAtFirstHole) {
  const std::string kTest = Preamble() +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 1
               ; Leave a hole at (0, 2)
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 3
               OpDecorate %104 DescriptorSet 0
               OpDecorate %104 Binding 4

      ; There was a hole at binding 2.  The ripple stops there.
      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 2
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 3
      ; CHECK: OpDecorate %104 DescriptorSet 0
      ; CHECK: OpDecorate %104 Binding 4

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant ; sampler comes first
        %101 = OpVariable %p_i_ty UniformConstant
        %102 = OpVariable %pu_st_ty Uniform
        %103 = OpVariable %pb_st_ty StorageBuffer
        %104 = OpVariable %p_si_ty UniformConstant

       %main = OpFunction %void None %voidfn
         %10 = OpLabel
         %11 = OpCopyObject %p_s_ty %100
         %12 = OpCopyObject %p_i_ty %101
         %13 = OpCopyObject %pu_st_ty %102
         %14 = OpCopyObject %pb_st_ty %103
         %15 = OpCopyObject %p_si_ty %104
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest, OneEntryPoint_MultiConflict_Resolves) {
  const std::string kTest = Preamble() +
                            R"(
      ; Two conflicts: at Bindings 0, and 1
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 1
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 1
               OpDecorate %104 DescriptorSet 0
               OpDecorate %104 Binding 2

      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 2
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 3
      ; CHECK: OpDecorate %104 DescriptorSet 0
      ; CHECK: OpDecorate %104 Binding 4

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant ; sampler first
        %101 = OpVariable %p_i_ty UniformConstant
        %102 = OpVariable %p_i_ty UniformConstant
        %103 = OpVariable %p_s_ty UniformConstant ; sampler second
        %104 = OpVariable %pu_st_ty Uniform

       %main = OpFunction %void None %voidfn
         %10 = OpLabel
         %11 = OpCopyObject %p_s_ty %100
         %12 = OpCopyObject %p_i_ty %101
         %13 = OpCopyObject %p_i_ty %102
         %14 = OpCopyObject %p_s_ty %103
         %15 = OpCopyObject %pu_st_ty %104
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest,
       OneEntryPoint_MultiConflict_ComplexCallGraph_Resolves) {
  // Check that uses are seen even when used at various points in a complex call
  // graph.
  const std::string kTest = Preamble() +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 1
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 1
               OpDecorate %104 DescriptorSet 0
               OpDecorate %104 Binding 2

      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 2
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 3
      ; CHECK: OpDecorate %104 DescriptorSet 0
      ; CHECK: OpDecorate %104 Binding 4

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant ; used in %200
        %101 = OpVariable %p_i_ty UniformConstant ; used in %300, %400
        %102 = OpVariable %p_i_ty UniformConstant ; used in %500
        %103 = OpVariable %p_s_ty UniformConstant ; used in %400 twice
        %104 = OpVariable %pu_st_ty Uniform       ; used in %600

        %200 = OpFunction %void None %voidfn
        %201 = OpLabel
        %202 = OpCopyObject %p_s_ty %100
               OpReturn
               OpFunctionEnd

        %300 = OpFunction %void None %voidfn
        %301 = OpLabel
        %302 = OpCopyObject %p_i_ty %101
               OpReturn
               OpFunctionEnd

        %400 = OpFunction %void None %voidfn
        %401 = OpLabel
        %402 = OpFunctionCall %void %200
        %403 = OpCopyObject %p_s_ty %103
        %404 = OpCopyObject %p_i_ty %101
        %405 = OpCopyObject %p_s_ty %103
        %406 = OpFunctionCall %void %300
               OpReturn
               OpFunctionEnd

        %500 = OpFunction %void None %voidfn
        %501 = OpLabel
        %502 = OpFunctionCall %void %400
        %503 = OpCopyObject %p_i_ty %102
        %504 = OpFunctionCall %void %300
               OpReturn
               OpFunctionEnd

        %600 = OpFunction %void None %voidfn
        %601 = OpLabel
        %602 = OpFunctionCall %void %300
        %603 = OpFunctionCall %void %500
        %604 = OpCopyObject %pu_st_ty %104
               OpReturn
               OpFunctionEnd

       %main = OpFunction %void None %voidfn
       %1000 = OpLabel
       %1001 = OpFunctionCall %void %600
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest,
       MultiEntryPoint_DuplicatConflicts_ResolvesOnlyOnce) {
  // Before:
  //
  //   Binding:   0          1
  //   Alpha:    %100,%101
  //   Beta:     %100,%101
  //
  // After:
  //
  //   Binding:   0          1
  //   Alpha:    %101       %100
  //   Beta:     %101       %100
  const std::string kTest = Preamble({"alpha", "beta"}) +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0

      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant
        %101 = OpVariable %p_i_ty UniformConstant

      %alpha = OpFunction %void None %voidfn
       %1000 = OpLabel
       %1001 = OpCopyObject %p_s_ty %100
       %1002 = OpCopyObject %p_i_ty %101
               OpReturn
               OpFunctionEnd

       %beta = OpFunction %void None %voidfn
       %2000 = OpLabel
       %2001 = OpCopyObject %p_s_ty %100
       %2002 = OpCopyObject %p_i_ty %101
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest,
       MultiEntryPoint_IndependentConflicts_Resolves) {
  // Before:
  //
  //   Binding:   0          1
  //   Alpha:    %100,%101
  //   Beta:     %102,%103
  //
  // After:
  //
  //   Binding:   0          1
  //   Alpha:    %101       %100
  //   Beta:     %102       %103
  const std::string kTest = Preamble({"alpha", "beta"}) +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 0
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 0

      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 0
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 1

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant
        %101 = OpVariable %p_i_ty UniformConstant
        %102 = OpVariable %p_i_ty UniformConstant
        %103 = OpVariable %p_s_ty UniformConstant

      %alpha = OpFunction %void None %voidfn
       %1000 = OpLabel
       %1001 = OpCopyObject %p_s_ty %100
       %1002 = OpCopyObject %p_i_ty %101
               OpReturn
               OpFunctionEnd

       %beta = OpFunction %void None %voidfn
       %2000 = OpLabel
       %2001 = OpCopyObject %p_i_ty %102
       %2002 = OpCopyObject %p_s_ty %103
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest,
       MultiEntryPoint_SameVarConflictsAcrossMultiEntryPoints_Resolves) {
  // A sampler variable is bumped, causing potential conflicts in other shaders.
  //
  // Before:
  //
  //   Binding:   0          1        2
  //   Alpha:    %100,%101
  //   Beta:     %100       %102
  //   Gamma:    %100                %103
  //
  // After:
  //
  //   Binding:   0          1        2
  //   Alpha:    %101       %100
  //   Beta:                %100     %102
  //   Gamma:    %100                %103
  //
  const std::string kTest = Preamble({"alpha", "beta", "gamma"}) +
                            R"(
               OpDecorate %100 DescriptorSet 0  ; The sampler
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 1
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 2

      ; bumped once
      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1

      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0

      ; pushed back from bump of %100
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 2

      ; does not need to be bumped
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 2

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant ; used in alpha, beta, gamma
        %101 = OpVariable %p_i_ty UniformConstant ; used in alpha
        %102 = OpVariable %pu_st_ty Uniform       ; used in beta
        %103 = OpVariable %pb_st_ty StorageBuffer ; used in gamma

      %alpha = OpFunction %void None %voidfn
       %1000 = OpLabel
       %1001 = OpCopyObject %p_s_ty %100
       %1002 = OpCopyObject %p_i_ty %101
               OpReturn
               OpFunctionEnd

       %beta = OpFunction %void None %voidfn
       %2000 = OpLabel
       %2001 = OpCopyObject %p_s_ty %100
       %2002 = OpCopyObject %pu_st_ty %102
               OpReturn
               OpFunctionEnd

      %gamma = OpFunction %void None %voidfn
       %3000 = OpLabel
       %3001 = OpCopyObject %p_s_ty %100
       %3002 = OpCopyObject %pb_st_ty %103
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest, MultiEntryPoint_ConflictCascade_Resolves) {
  // Before:
  //
  //   Binding:   0          1        2        3
  //   Alpha:    %100,%101
  //   Beta:     %100       %102
  //   Gamma:               %102     %103
  //   Delta:                        %103     %104
  //
  // After:
  //
  //   Binding:   0          1        2        3        4
  //   Alpha:    %101       %100
  //   Beta:                %100     %102
  //   Gamma:                        %102     %103
  //   Delta:                                 %103     %104
  //
  const std::string kTest = Preamble({"alpha", "beta", "gamma", "delta"}) +
                            R"(
               OpDecorate %100 DescriptorSet 0  ; The sampler
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 1
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 2
               OpDecorate %104 DescriptorSet 0
               OpDecorate %104 Binding 3

      ; %100 is bumped once:
      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 1

      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 0

      ; pushed back from bump of %100
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 2

      ; pushed back from bump of %102
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 3

      ; pushed back from bump of %103
      ; CHECK: OpDecorate %104 DescriptorSet 0
      ; CHECK: OpDecorate %104 Binding 4

)" + BasicTypes() + R"(

        %100 = OpVariable %p_s_ty UniformConstant  ; used in alpha, beta
        %101 = OpVariable %p_i_ty UniformConstant  ; used in alpha
        %102 = OpVariable %pu_st_ty Uniform        ; used in beta, gamma
        %103 = OpVariable %pb_st_ty StorageBuffer  ; used in gamma, delta
        %104 = OpVariable %p_si_ty UniformConstant ; used delta

      %alpha = OpFunction %void None %voidfn
       %1000 = OpLabel
       %1001 = OpCopyObject %p_s_ty %100
       %1002 = OpCopyObject %p_i_ty %101
               OpReturn
               OpFunctionEnd

       %beta = OpFunction %void None %voidfn
       %2000 = OpLabel
       %2001 = OpCopyObject %p_s_ty %100
       %2002 = OpCopyObject %pu_st_ty %102
               OpReturn
               OpFunctionEnd

      %gamma = OpFunction %void None %voidfn
       %3000 = OpLabel
       %3001 = OpCopyObject %pu_st_ty %102
       %3002 = OpCopyObject %pb_st_ty %103
               OpReturn
               OpFunctionEnd

      %delta = OpFunction %void None %voidfn
       %4000 = OpLabel
       %4001 = OpCopyObject %pb_st_ty %103
       %4002 = OpCopyObject %p_si_ty %104
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

TEST_F(ResolveBindingConflictsTest,
       MultiEntryPoint_ConflictCascade_RevisitEntryPoint) {
  // Prove that the settling algorithm knows to revisit entry points that
  // already had all their own conflicts resolved.
  //
  // Before:
  //
  //   Binding:   0          1       2        3       4
  //   Alpha:    %100,%101          %103     %104    %105
  //   Beta:          %101  %102    %103             %105
  //
  // After:
  //
  //   Binding:    0         1       2        3       4       5
  //   Alpha:    %100       %101             %103    %104    %105
  //   Beta:                %101    %102     %103            %105
  const std::string kTest = Preamble({"alpha", "beta"}) +
                            R"(
               OpDecorate %100 DescriptorSet 0
               OpDecorate %100 Binding 0
               OpDecorate %101 DescriptorSet 0 ; the sampler
               OpDecorate %101 Binding 0
               OpDecorate %102 DescriptorSet 0
               OpDecorate %102 Binding 1
               OpDecorate %103 DescriptorSet 0
               OpDecorate %103 Binding 2
               OpDecorate %104 DescriptorSet 0
               OpDecorate %104 Binding 3
               OpDecorate %105 DescriptorSet 0
               OpDecorate %105 Binding 4

      ; CHECK: OpDecorate %100 DescriptorSet 0
      ; CHECK: OpDecorate %100 Binding 0
      ; CHECK: OpDecorate %101 DescriptorSet 0
      ; CHECK: OpDecorate %101 Binding 1
      ; CHECK: OpDecorate %102 DescriptorSet 0
      ; CHECK: OpDecorate %102 Binding 2
      ; CHECK: OpDecorate %103 DescriptorSet 0
      ; CHECK: OpDecorate %103 Binding 3
      ; CHECK: OpDecorate %104 DescriptorSet 0
      ; CHECK: OpDecorate %104 Binding 4
      ; CHECK: OpDecorate %105 DescriptorSet 0
      ; CHECK: OpDecorate %105 Binding 5

)" + BasicTypes() + R"(

        %100 = OpVariable %p_i_ty UniformConstant
        %101 = OpVariable %p_s_ty UniformConstant
        %102 = OpVariable %pu_st_ty Uniform
        %103 = OpVariable %pb_st_ty StorageBuffer
        %104 = OpVariable %p_si_ty UniformConstant
        %105 = OpVariable %p_s_ty UniformConstant

      %alpha = OpFunction %void None %voidfn
       %1000 = OpLabel
       %1001 = OpCopyObject %p_i_ty %100
       %1002 = OpCopyObject %p_s_ty %101
       %1003 = OpCopyObject %pb_st_ty %103
       %1004 = OpCopyObject %p_si_ty %104
       %1005 = OpCopyObject %p_s_ty %105
               OpReturn
               OpFunctionEnd

       %beta = OpFunction %void None %voidfn
       %2000 = OpLabel
       %2001 = OpCopyObject %p_s_ty %101
       %2002 = OpCopyObject %pu_st_ty %102
       %2003 = OpCopyObject %pb_st_ty %103
       %2004 = OpCopyObject %p_s_ty %105
               OpReturn
               OpFunctionEnd
)";

  auto [disasm, status] = SinglePassRunAndMatch<ResolveBindingConflictsPass>(
      kTest, /* do_validation= */ true);
  EXPECT_EQ(status, Pass::Status::SuccessWithChange) << disasm;
}

}  // namespace
}  // namespace opt
}  // namespace spvtools