// Copyright (c) 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <sstream>
#include <string>
#include <vector>

#include "pass_fixture.h"
#include "pass_utils.h"
#include "source/opt/graphics_robust_access_pass.h"

namespace {

using namespace spvtools;

using opt::GraphicsRobustAccessPass;
using GraphicsRobustAccessTest = opt::PassTest<::testing::Test>;

// Test incompatible module, determined at module-level.

TEST_F(GraphicsRobustAccessTest, FailNotShader) {
  const std::string text = R"(
; CHECK: Can only process Shader modules
OpCapability Kernel
)";

  SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
}

TEST_F(GraphicsRobustAccessTest, FailCantProcessVariablePointers) {
  const std::string text = R"(
; CHECK: Can't process modules with VariablePointers capability
OpCapability VariablePointers
)";

  SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
}

TEST_F(GraphicsRobustAccessTest, FailCantProcessVariablePointersStorageBuffer) {
  const std::string text = R"(
; CHECK: Can't process modules with VariablePointersStorageBuffer capability
OpCapability VariablePointersStorageBuffer
)";

  SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
}

TEST_F(GraphicsRobustAccessTest, FailCantProcessRuntimeDescriptorArrayEXT) {
  const std::string text = R"(
; CHECK: Can't process modules with RuntimeDescriptorArrayEXT capability
OpCapability RuntimeDescriptorArrayEXT
)";

  SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
}

TEST_F(GraphicsRobustAccessTest, FailCantProcessPhysical32AddressingModel) {
  const std::string text = R"(
; CHECK: Addressing model must be Logical.  Found OpMemoryModel Physical32 OpenCL
OpCapability Shader
OpMemoryModel Physical32 OpenCL
)";

  SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
}

TEST_F(GraphicsRobustAccessTest, FailCantProcessPhysical64AddressingModel) {
  const std::string text = R"(
; CHECK: Addressing model must be Logical.  Found OpMemoryModel Physical64 OpenCL
OpCapability Shader
OpMemoryModel Physical64 OpenCL
)";

  SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
}

TEST_F(GraphicsRobustAccessTest,
       FailCantProcessPhysicalStorageBuffer64EXTAddressingModel) {
  const std::string text = R"(
; CHECK: Addressing model must be Logical.  Found OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpCapability Shader
OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
)";

  SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
}

// Test access chains

// Returns the names of access chain instructions handled by the pass.
// For the purposes of this pass, regular and in-bounds access chains are the
// same.)
std::vector<const char*> AccessChains() {
  return {"OpAccessChain", "OpInBoundsAccessChain"};
}

std::string ShaderPreamble() {
  return R"(
  OpCapability Shader
  OpMemoryModel Logical Simple
  OpEntryPoint GLCompute %main "main"
)";
}

std::string ShaderPreamble(const std::vector<std::string>& names) {
  std::ostringstream os;
  os << ShaderPreamble();
  for (auto& name : names) {
    os << "  OpName %" << name << " \"" << name << "\"\n";
  }
  return os.str();
}

std::string ShaderPreambleAC() {
  return ShaderPreamble({"ac", "ptr_ty", "var"});
}

std::string ShaderPreambleAC(const std::vector<std::string>& names) {
  auto names2 = names;
  names2.push_back("ac");
  names2.push_back("ptr_ty");
  names2.push_back("var");
  return ShaderPreamble(names2);
}

std::string DecoSSBO() {
  return R"(
  OpDecorate %ssbo_s BufferBlock
  OpMemberDecorate %ssbo_s 0 Offset 0
  OpMemberDecorate %ssbo_s 1 Offset 4
  OpMemberDecorate %ssbo_s 2 Offset 16
  OpDecorate %var DescriptorSet 0
  OpDecorate %var Binding 0
)";
}

std::string TypesVoid() {
  return R"(
  %void = OpTypeVoid
  %void_fn = OpTypeFunction %void
)";
}

std::string TypesInt() {
  return R"(
  %uint = OpTypeInt 32 0
  %int = OpTypeInt 32 1
)";
}

std::string TypesFloat() {
  return R"(
  %float = OpTypeFloat 32
)";
}

std::string TypesShort() {
  return R"(
  %ushort = OpTypeInt 16 0
  %short = OpTypeInt 16 1
)";
}

std::string TypesLong() {
  return R"(
  %ulong = OpTypeInt 64 0
  %long = OpTypeInt 64 1
)";
}

std::string MainPrefix() {
  return R"(
  %main = OpFunction %void None %void_fn
  %entry = OpLabel
)";
}

std::string MainSuffix() {
  return R"(
  OpReturn
  OpFunctionEnd
)";
}

std::string ACCheck(const std::string& access_chain_inst,
                    const std::string& original,
                    const std::string& transformed) {
  return "\n ; CHECK: %ac = " + access_chain_inst + " %ptr_ty %var" +
         (transformed.empty() ? "" : " ") + transformed +
         "\n ; CHECK-NOT: " + access_chain_inst +
         "\n ; CHECK-NEXT: OpReturn"
         "\n %ac = " +
         access_chain_inst + " %ptr_ty %var " + (original.empty() ? "" : " ") +
         original + "\n";
}

std::string ACCheckFail(const std::string& access_chain_inst,
                        const std::string& original,
                        const std::string& transformed) {
  return "\n ; CHECK: %ac = " + access_chain_inst + " %ptr_ty %var" +
         (transformed.empty() ? "" : " ") + transformed +
         "\n ; CHECK-NOT: " + access_chain_inst +
         "\n ; CHECK-NOT: OpReturn"
         "\n %ac = " +
         access_chain_inst + " %ptr_ty %var " + (original.empty() ? "" : " ") +
         original + "\n";
}

// Access chain into:
//   Vector
//     Vector sizes 2, 3, 4
//   Matrix
//     Matrix columns 2, 4
//     Component is vector 2, 4
//   Array
//   Struct
//   TODO(dneto): RuntimeArray

TEST_F(GraphicsRobustAccessTest, ACVectorLeastInboundConstantUntouched) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
       %uvec2 = OpTypeVector %uint 2
       %var_ty = OpTypePointer Function %uvec2
       %ptr_ty = OpTypePointer Function %uint
       %uint_0 = OpConstant %uint 0
       )"
            << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_0", "%uint_0")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACVectorMostInboundConstantUntouched) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
       %v4uint = OpTypeVector %uint 4
       %var_ty = OpTypePointer Function %v4uint
       %ptr_ty = OpTypePointer Function %uint
       %uint_3 = OpConstant %uint 3
       )"
            << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_3", "%uint_3")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACVectorExcessConstantClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
       %v4uint = OpTypeVector %uint 4
       %var_ty = OpTypePointer Function %v4uint
       %ptr_ty = OpTypePointer Function %uint
       %uint_4 = OpConstant %uint 4
       )"
            << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_4", "%int_3")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACVectorNegativeConstantClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
       %v4uint = OpTypeVector %uint 4
       %var_ty = OpTypePointer Function %v4uint
       %ptr_ty = OpTypePointer Function %uint
       %int_n1 = OpConstant %int -1
       )"
            << MainPrefix() << R"(
       ; CHECK: %int_0 = OpConstant %int 0
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%int_n1", "%int_0")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

// Like the previous test, but ensures the pass knows how to modify an index
// which does not come first in the access chain.
TEST_F(GraphicsRobustAccessTest, ACVectorInArrayNegativeConstantClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
       %v4uint = OpTypeVector %uint 4
       %uint_1 = OpConstant %uint 1
       %uint_2 = OpConstant %uint 2
       %arr = OpTypeArray %v4uint %uint_2
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %uint
       %int_n1 = OpConstant %int -1
       )"
            << MainPrefix() << R"(
       ; CHECK: %int_0 = OpConstant %int 0
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%uint_1 %int_n1", "%uint_1 %int_0") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACVectorGeneralClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() << R"(
       %v4uint = OpTypeVector %uint 4
       %var_ty = OpTypePointer Function %v4uint
       %ptr_ty = OpTypePointer Function %uint
       %i = OpUndef %int)"
            << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_3 = OpConstant %int 3
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %int_3
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACVectorGeneralShortClamped) {
  // Show that signed 16 bit integers are clamped as well.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesShort() <<
        R"(
       %v4short = OpTypeVector %short 4
       %var_ty = OpTypePointer Function %v4short
       %ptr_ty = OpTypePointer Function %short
       %i = OpUndef %short)"
            << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-NOT: = OpTypeInt 32
       ; CHECK-DAG: %short_0 = OpConstant %short 0
       ; CHECK-DAG: %short_3 = OpConstant %short 3
       ; CHECK-NOT: = OpTypeInt 32
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %short %[[GLSLSTD450]] SClamp %i %short_0 %short_3
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACVectorGeneralUShortClamped) {
  // Show that unsigned 16 bit integers are clamped as well.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesShort() <<
        R"(
       %v4ushort = OpTypeVector %ushort 4
       %var_ty = OpTypePointer Function %v4ushort
       %ptr_ty = OpTypePointer Function %ushort
       %i = OpUndef %ushort)"
            << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-NOT: = OpTypeInt 32
       ; CHECK-DAG: %short_0 = OpConstant %short 0
       ; CHECK-DAG: %short_3 = OpConstant %short 3
       ; CHECK-NOT: = OpTypeInt 32
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %ushort %[[GLSLSTD450]] SClamp %i %short_0 %short_3
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACVectorGeneralLongClamped) {
  // Show that signed 64 bit integers are clamped as well.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int64\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesLong() <<
        R"(
       %v4long = OpTypeVector %long 4
       %var_ty = OpTypePointer Function %v4long
       %ptr_ty = OpTypePointer Function %long
       %i = OpUndef %long)"
            << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-NOT: = OpTypeInt 32
       ; CHECK-DAG: %long_0 = OpConstant %long 0
       ; CHECK-DAG: %long_3 = OpConstant %long 3
       ; CHECK-NOT: = OpTypeInt 32
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] SClamp %i %long_0 %long_3
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACVectorGeneralULongClamped) {
  // Show that unsigned 64 bit integers are clamped as well.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int64\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesLong() <<
        R"(
       %v4ulong = OpTypeVector %ulong 4
       %var_ty = OpTypePointer Function %v4ulong
       %ptr_ty = OpTypePointer Function %ulong
       %i = OpUndef %ulong)"
            << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-NOT: = OpTypeInt 32
       ; CHECK-DAG: %long_0 = OpConstant %long 0
       ; CHECK-DAG: %long_3 = OpConstant %long 3
       ; CHECK-NOT: = OpTypeInt 32
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] SClamp %i %long_0 %long_3
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACMatrixLeastInboundConstantUntouched) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %v2float = OpTypeVector %float 2
       %mat4v2float = OpTypeMatrix %v2float 4
       %var_ty = OpTypePointer Function %mat4v2float
       %ptr_ty = OpTypePointer Function %float
       %uint_0 = OpConstant %uint 0
       %uint_1 = OpConstant %uint 1
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%uint_0 %uint_1", "%uint_0 %uint_1")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACMatrixMostInboundConstantUntouched) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %v2float = OpTypeVector %float 2
       %mat4v2float = OpTypeMatrix %v2float 4
       %var_ty = OpTypePointer Function %mat4v2float
       %ptr_ty = OpTypePointer Function %float
       %uint_1 = OpConstant %uint 1
       %uint_3 = OpConstant %uint 3
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%uint_3 %uint_1", "%uint_3 %uint_1")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACMatrixExcessConstantClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %v2float = OpTypeVector %float 2
       %mat4v2float = OpTypeMatrix %v2float 4
       %var_ty = OpTypePointer Function %mat4v2float
       %ptr_ty = OpTypePointer Function %float
       %uint_1 = OpConstant %uint 1
       %uint_4 = OpConstant %uint 4
       )" << MainPrefix() << R"(
       ; CHECK: %int_3 = OpConstant %int 3
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%uint_4 %uint_1", "%int_3 %uint_1") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACMatrixNegativeConstantClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %v2float = OpTypeVector %float 2
       %mat4v2float = OpTypeMatrix %v2float 4
       %var_ty = OpTypePointer Function %mat4v2float
       %ptr_ty = OpTypePointer Function %float
       %uint_1 = OpConstant %uint 1
       %int_n1 = OpConstant %int -1
       )" << MainPrefix() << R"(
       ; CHECK: %int_0 = OpConstant %int 0
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%int_n1 %uint_1", "%int_0 %uint_1") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACMatrixGeneralClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %v2float = OpTypeVector %float 2
       %mat4v2float = OpTypeMatrix %v2float 4
       %var_ty = OpTypePointer Function %mat4v2float
       %ptr_ty = OpTypePointer Function %float
       %uint_1 = OpConstant %uint 1
       %i = OpUndef %int
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_3 = OpConstant %int 3
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %int_3
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i %uint_1", "%[[clamp]] %uint_1") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayLeastInboundConstantUntouched) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %uint_200 = OpConstant %uint 200
       %arr = OpTypeArray %float %uint_200
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %int_0 = OpConstant %int 0
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%int_0", "%int_0") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayMostInboundConstantUntouched) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %uint_200 = OpConstant %uint 200
       %arr = OpTypeArray %float %uint_200
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %int_199 = OpConstant %int 199
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%int_199", "%int_199") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayGeneralClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %uint_200 = OpConstant %uint 200
       %arr = OpTypeArray %float %uint_200
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %int
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_199 = OpConstant %int 199
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %int_199
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayGeneralShortIndexUIntBoundsClamped) {
  // Index is signed short, array bounds overflows the index type.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesShort() << TypesFloat() << R"(
       %uint_70000 = OpConstant %uint 70000 ; overflows 16bits
       %arr = OpTypeArray %float %uint_70000
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %short
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_69999 = OpConstant %int 69999
       ; CHECK: OpLabel
       ; CHECK: %[[i_ext:\w+]] = OpSConvert %uint %i
       ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] SClamp %[[i_ext]] %int_0 %int_69999
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayGeneralUShortIndexIntBoundsClamped) {
  // Index is unsigned short, array bounds overflows the index type.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesShort() << TypesFloat() << R"(
       %int_70000 = OpConstant %int 70000 ; overflows 16bits
       %arr = OpTypeArray %float %int_70000
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %ushort
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_69999 = OpConstant %int 69999
       ; CHECK: OpLabel
       ; CHECK: %[[i_ext:\w+]] = OpUConvert %uint %i
       ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] SClamp %[[i_ext]] %int_0 %int_69999
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayGeneralUIntIndexShortBoundsClamped) {
  // Signed int index i is wider than the array bounds type.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesShort() << TypesFloat() << R"(
       %short_200 = OpConstant %short 200
       %arr = OpTypeArray %float %short_200
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %uint
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_199 = OpConstant %int 199
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] SClamp %i %int_0 %int_199
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayGeneralIntIndexUShortBoundsClamped) {
  // Unsigned int index i is wider than the array bounds type.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesShort() << TypesFloat() << R"(
       %ushort_200 = OpConstant %ushort 200
       %arr = OpTypeArray %float %ushort_200
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %int
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_199 = OpConstant %int 199
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %int_199
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayGeneralLongIndexUIntBoundsClamped) {
  // Signed long index i is wider than the array bounds type.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int64\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesLong() << TypesFloat() << R"(
       %uint_200 = OpConstant %uint 200
       %arr = OpTypeArray %float %uint_200
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %long
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %long_0 = OpConstant %long 0
       ; CHECK-DAG: %long_199 = OpConstant %long 199
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] SClamp %i %long_0 %long_199
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayGeneralULongIndexIntBoundsClamped) {
  // Unsigned long index i is wider than the array bounds type.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int64\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesLong() << TypesFloat() << R"(
       %int_200 = OpConstant %int 200
       %arr = OpTypeArray %float %int_200
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %ulong
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %long_0 = OpConstant %long 0
       ; CHECK-DAG: %long_199 = OpConstant %long 199
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] SClamp %i %long_0 %long_199
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest,
       ACArrayGeneralShortIndeArrayBiggerThanShortMaxClipsToShortIntMax) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesShort()
            << TypesInt() << TypesFloat() << R"(
       %uint_50000 = OpConstant %uint 50000
       %arr = OpTypeArray %float %uint_50000
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %ushort
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %short_0 = OpConstant %short 0
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %short 32767
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %ushort %[[GLSLSTD450]] SClamp %i %short_0 %[[intmax]]
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest,
       ACArrayGeneralIntIndexArrayBiggerThanIntMaxClipsToSignedIntMax) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %uint_3000000000 = OpConstant %uint 3000000000
       %arr = OpTypeArray %float %uint_3000000000
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %uint
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %int 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] SClamp %i %int_0 %[[intmax]]
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest,
       ACArrayGeneralLongIndexArrayBiggerThanLongMaxClipsToSignedLongMax) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int64\n"
            << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
            << TypesLong()
            << TypesFloat()
            // 2^63 == 9,223,372,036,854,775,807
            << R"(
       %ulong_9223372036854775999 = OpConstant %ulong 9223372036854775999
       %arr = OpTypeArray %float %ulong_9223372036854775999
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %ulong
       )"
            << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %long_0 = OpConstant %long 0
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %long 9223372036854775807
       ; CHECK: OpLabel
       ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] SClamp %i %long_0 %[[intmax]]
       %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArraySpecIdSizedAlwaysClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"spec200"}) << R"(
       OpDecorate %spec200 SpecId 0 )" << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %spec200 = OpSpecConstant %int 200
       %arr = OpTypeArray %float %spec200
       %var_ty = OpTypePointer Function %arr
       %ptr_ty = OpTypePointer Function %float
       %uint_5 = OpConstant %uint 5
       )" << MainPrefix() << R"(
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %uint_0 = OpConstant %uint 0
       ; CHECK-DAG: %uint_1 = OpConstant %uint 1
       ; CHECK-DAG: %[[uint_intmax:\w+]] = OpConstant %uint 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[max:\w+]] = OpISub %uint %spec200 %uint_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UMin %[[max]] %[[uint_intmax]]
       ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] SClamp %uint_5 %uint_0 %[[smin]]
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%uint_5", "%[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACStructLeastUntouched) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %struct = OpTypeStruct %float %float %float
       %var_ty = OpTypePointer Function %struct
       %ptr_ty = OpTypePointer Function %float
       %int_0 = OpConstant %int 0
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%int_0", "%int_0") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACStructMostUntouched) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %struct = OpTypeStruct %float %float %float
       %var_ty = OpTypePointer Function %struct
       %ptr_ty = OpTypePointer Function %float
       %int_2 = OpConstant %int 2
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function)"
            << ACCheck(ac, "%int_2", "%int_2") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACStructSpecConstantFail) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"struct", "spec200"})
            << "OpDecorate %spec200 SpecId 0\n"
            <<

        TypesVoid() << TypesInt() << TypesFloat() << R"(
       %spec200 = OpSpecConstant %int 200
       %struct = OpTypeStruct %float %float %float
       %var_ty = OpTypePointer Function %struct
       %ptr_ty = OpTypePointer Function %float
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function
       ; CHECK: Member index into struct is not a constant integer
       ; CHECK-SAME: %spec200 = OpSpecConstant %int 200
       )"
            << ACCheckFail(ac, "%spec200", "%spec200") << MainSuffix();
    SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
  }
}

TEST_F(GraphicsRobustAccessTest, ACStructFloatConstantFail) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"struct"}) <<

        TypesVoid() << TypesInt() << TypesFloat() << R"(
       %float_2 = OpConstant %float 2
       %struct = OpTypeStruct %float %float %float
       %var_ty = OpTypePointer Function %struct
       %ptr_ty = OpTypePointer Function %float
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function
       ; CHECK: Member index into struct is not a constant integer
       ; CHECK-SAME: %float_2 = OpConstant %float 2
       )"
            << ACCheckFail(ac, "%float_2", "%float_2") << MainSuffix();
    SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
  }
}

TEST_F(GraphicsRobustAccessTest, ACStructNonConstantFail) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"struct", "i"}) <<

        TypesVoid() << TypesInt() << TypesFloat() << R"(
       %float_2 = OpConstant %float 2
       %struct = OpTypeStruct %float %float %float
       %var_ty = OpTypePointer Function %struct
       %ptr_ty = OpTypePointer Function %float
       %i = OpUndef %int
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function
       ; CHECK: Member index into struct is not a constant integer
       ; CHECK-SAME: %i = OpUndef %int
       )"
            << ACCheckFail(ac, "%i", "%i") << MainSuffix();
    SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
  }
}

TEST_F(GraphicsRobustAccessTest, ACStructExcessFail) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"struct", "i"}) << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %struct = OpTypeStruct %float %float %float
       %var_ty = OpTypePointer Function %struct
       %ptr_ty = OpTypePointer Function %float
       %i = OpConstant %int 4
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function
       ; CHECK: Member index 4 is out of bounds for struct type:
       ; CHECK-SAME: %struct = OpTypeStruct %float %float %float
       )"
            << ACCheckFail(ac, "%i", "%i") << MainSuffix();
    SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
  }
}

TEST_F(GraphicsRobustAccessTest, ACStructNegativeFail) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"struct", "i"}) << TypesVoid() << TypesInt()
            << TypesFloat() << R"(
       %struct = OpTypeStruct %float %float %float
       %var_ty = OpTypePointer Function %struct
       %ptr_ty = OpTypePointer Function %float
       %i = OpConstant %int -1
       )" << MainPrefix() << R"(
       %var = OpVariable %var_ty Function
       ; CHECK: Member index -1 is out of bounds for struct type:
       ; CHECK-SAME: %struct = OpTypeStruct %float %float %float
       )"
            << ACCheckFail(ac, "%i", "%i") << MainSuffix();
    SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
  }
}

TEST_F(GraphicsRobustAccessTest, ACRTArrayLeastInboundClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC() << "OpDecorate %rtarr ArrayStride 4 "
            << DecoSSBO() << TypesVoid() << TypesInt() << TypesFloat() << R"(
       %rtarr = OpTypeRuntimeArray %float
       %ssbo_s = OpTypeStruct %uint %uint %rtarr
       %var_ty = OpTypePointer Uniform %ssbo_s
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_0 = OpConstant %int 0
       %int_2 = OpConstant %int 2
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_1 = OpConstant %int 1
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %int 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
       ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %int %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %int_0 %int_0 %[[smin]]
       )"
            << MainPrefix() << ACCheck(ac, "%int_2 %int_0", "%int_2 %[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralShortIndexClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << "OpDecorate %rtarr ArrayStride 4 "
            << DecoSSBO() << TypesVoid() << TypesShort() << TypesFloat() << R"(
       %rtarr = OpTypeRuntimeArray %float
       %ssbo_s = OpTypeStruct %short %short %rtarr
       %var_ty = OpTypePointer Uniform %ssbo_s
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %short_2 = OpConstant %short 2
       %i = OpUndef %short
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK: %uint = OpTypeInt 32 0
       ; CHECK-DAG: %uint_1 = OpConstant %uint 1
       ; CHECK-DAG: %uint_0 = OpConstant %uint 0
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %uint 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
       ; CHECK-DAG: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1
       ; CHECK-DAG: %[[i_ext:\w+]] = OpSConvert %uint %i
       ; CHECK: %[[smin:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] SClamp %[[i_ext]] %uint_0 %[[smin]]
       )"
            << MainPrefix() << ACCheck(ac, "%short_2 %i", "%short_2 %[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralUShortIndexClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int16\n"
            << ShaderPreambleAC({"i"}) << "OpDecorate %rtarr ArrayStride 4 "
            << DecoSSBO() << TypesVoid() << TypesShort() << TypesFloat() << R"(
       %rtarr = OpTypeRuntimeArray %float
       %ssbo_s = OpTypeStruct %short %short %rtarr
       %var_ty = OpTypePointer Uniform %ssbo_s
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %short_2 = OpConstant %short 2
       %i = OpUndef %ushort
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK: %uint = OpTypeInt 32 0
       ; CHECK-DAG: %uint_1 = OpConstant %uint 1
       ; CHECK-DAG: %uint_0 = OpConstant %uint 0
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %uint 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
       ; CHECK-DAG: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1
       ; CHECK-DAG: %[[i_ext:\w+]] = OpSConvert %uint %i
       ; CHECK: %[[smin:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] SClamp %[[i_ext]] %uint_0 %[[smin]]
       )"
            << MainPrefix() << ACCheck(ac, "%short_2 %i", "%short_2 %[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralIntIndexClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i"}) << "OpDecorate %rtarr ArrayStride 4 "
            << DecoSSBO() << TypesVoid() << TypesInt() << TypesFloat() << R"(
       %rtarr = OpTypeRuntimeArray %float
       %ssbo_s = OpTypeStruct %int %int %rtarr
       %var_ty = OpTypePointer Uniform %ssbo_s
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_2 = OpConstant %int 2
       %i = OpUndef %int
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_1 = OpConstant %int 1
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %int 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
       ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %int %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %[[smin]]
       )"
            << MainPrefix() << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralUIntIndexClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i"}) << "OpDecorate %rtarr ArrayStride 4 "
            << DecoSSBO() << TypesVoid() << TypesInt() << TypesFloat() << R"(
       %rtarr = OpTypeRuntimeArray %float
       %ssbo_s = OpTypeStruct %int %int %rtarr
       %var_ty = OpTypePointer Uniform %ssbo_s
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_2 = OpConstant %int 2
       %i = OpUndef %uint
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %uint_1 = OpConstant %uint 1
       ; CHECK-DAG: %uint_0 = OpConstant %uint 0
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %uint 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
       ; CHECK: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] SClamp %i %uint_0 %[[smin]]
       )"
            << MainPrefix() << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralLongIndexClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int64" << ShaderPreambleAC({"i"})
            << "OpDecorate %rtarr ArrayStride 4 " << DecoSSBO() << TypesVoid()
            << TypesInt() << TypesLong() << TypesFloat() << R"(
       %rtarr = OpTypeRuntimeArray %float
       %ssbo_s = OpTypeStruct %int %int %rtarr
       %var_ty = OpTypePointer Uniform %ssbo_s
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_2 = OpConstant %int 2
       %i = OpUndef %long
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %long_0 = OpConstant %long 0
       ; CHECK-DAG: %long_1 = OpConstant %long 1
       ; CHECK-DAG: %[[longmax:\w+]] = OpConstant %long 9223372036854775807
       ; CHECK: OpLabel
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
       ; CHECK: %[[arrlen_ext:\w+]] = OpUConvert %ulong %[[arrlen]]
       ; CHECK: %[[max:\w+]] = OpISub %long %[[arrlen_ext]] %long_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %long %[[GLSLSTD450]] UMin %[[max]] %[[longmax]]
       ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] SClamp %i %long_0 %[[smin]]
       )" << MainPrefix()
            << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralULongIndexClamped) {
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << "OpCapability Int64" << ShaderPreambleAC({"i"})
            << "OpDecorate %rtarr ArrayStride 4 " << DecoSSBO() << TypesVoid()
            << TypesInt() << TypesLong() << TypesFloat() << R"(
       %rtarr = OpTypeRuntimeArray %float
       %ssbo_s = OpTypeStruct %int %int %rtarr
       %var_ty = OpTypePointer Uniform %ssbo_s
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_2 = OpConstant %int 2
       %i = OpUndef %ulong
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %ulong_0 = OpConstant %ulong 0
       ; CHECK-DAG: %ulong_1 = OpConstant %ulong 1
       ; CHECK-DAG: %[[longmax:\w+]] = OpConstant %ulong 9223372036854775807
       ; CHECK: OpLabel
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
       ; CHECK: %[[arrlen_ext:\w+]] = OpUConvert %ulong %[[arrlen]]
       ; CHECK: %[[max:\w+]] = OpISub %ulong %[[arrlen_ext]] %ulong_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] UMin %[[max]] %[[longmax]]
       ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] SClamp %i %ulong_0 %[[smin]]
       )" << MainPrefix()
            << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]") << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACRTArrayStructVectorElem) {
  // The point of this test is that the access chain can have indices past the
  // index into the runtime array.  For good measure, the index into the final
  // struct is out of bounds.  We have to clamp that index too.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i", "j"})
            << "OpDecorate %rtarr ArrayStride 32\n"
            << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n"
            << "OpMemberDecorate %rtelem 1 Offset 16\n"
            << TypesVoid() << TypesInt() << TypesFloat() << R"(
       %v4float = OpTypeVector %float 4
       %rtelem = OpTypeStruct %v4float %v4float
       %rtarr = OpTypeRuntimeArray %rtelem
       %ssbo_s = OpTypeStruct %int %int %rtarr
       %var_ty = OpTypePointer Uniform %ssbo_s
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_1 = OpConstant %int 1
       %int_2 = OpConstant %int 2
       %i = OpUndef %int
       %j = OpUndef %int
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_3 = OpConstant %int 3
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %int 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
       ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %int %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %[[smin]]
       ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %j %int_0 %int_3
       )" << MainPrefix()
            << ACCheck(ac, "%int_2 %i %int_1 %j",
                       "%int_2 %[[clamp_i]] %int_1 %[[clamp_j]]")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACArrayRTArrayStructVectorElem) {
  // Now add an additional level of arrays around the Block-decorated struct.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i", "ssbo_s"})
            << "OpDecorate %rtarr ArrayStride 32\n"
            << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n"
            << "OpMemberDecorate %rtelem 1 Offset 16\n"
            << TypesVoid() << TypesInt() << TypesFloat() << R"(
       %v4float = OpTypeVector %float 4
       %rtelem = OpTypeStruct %v4float %v4float
       %rtarr = OpTypeRuntimeArray %rtelem
       %ssbo_s = OpTypeStruct %int %int %rtarr
       %arr_size = OpConstant %int 10
       %arr_ssbo = OpTypeArray %ssbo_s %arr_size
       %var_ty = OpTypePointer Uniform %arr_ssbo
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_1 = OpConstant %int 1
       %int_2 = OpConstant %int 2
       %int_17 = OpConstant %int 17
       %i = OpUndef %int
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %[[ssbo_p:\w+]] = OpTypePointer Uniform %ssbo_s
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_9 = OpConstant %int 9
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %int 2147483647
       ; CHECK: OpLabel
       ; This access chain is manufactured only so we can compute the array length.
       ; Note that the %int_9 is already clamped
       ; CHECK: %[[ssbo_base:\w+]] = )" << ac
            << R"( %[[ssbo_p]] %var %int_9
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %[[ssbo_base]] 2
       ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %int %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %[[smin]]
       )" << MainPrefix()
            << ACCheck(ac, "%int_17 %int_2 %i %int_1 %int_2",
                       "%int_9 %int_2 %[[clamp_i]] %int_1 %int_2")
            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, ACSplitACArrayRTArrayStructVectorElem) {
  // Split the address calculation across two access chains.  Force
  // the transform to walk up the access chains to find the base variable.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i", "j", "k", "ssbo_s", "ssbo_pty",
                                 "rtarr_pty", "ac_ssbo", "ac_rtarr"})
            << "OpDecorate %rtarr ArrayStride 32\n"
            << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n"
            << "OpMemberDecorate %rtelem 1 Offset 16\n"
            << TypesVoid() << TypesInt() << TypesFloat() << R"(
       %v4float = OpTypeVector %float 4
       %rtelem = OpTypeStruct %v4float %v4float
       %rtarr = OpTypeRuntimeArray %rtelem
       %ssbo_s = OpTypeStruct %int %int %rtarr
       %arr_size = OpConstant %int 10
       %arr_ssbo = OpTypeArray %ssbo_s %arr_size
       %var_ty = OpTypePointer Uniform %arr_ssbo
       %ssbo_pty = OpTypePointer Uniform %ssbo_s
       %rtarr_pty = OpTypePointer Uniform %rtarr
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_1 = OpConstant %int 1
       %int_2 = OpConstant %int 2
       %i = OpUndef %int
       %j = OpUndef %int
       %k = OpUndef %int
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_9 = OpConstant %int 9
       ; CHECK-DAG: %int_3 = OpConstant %int 3
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %int 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %int_9
       ; CHECK: %ac_ssbo = )" << ac
            << R"( %ssbo_pty %var %[[clamp_i]]
       ; CHECK: %ac_rtarr = )"
            << ac << R"( %rtarr_pty %ac_ssbo %int_2

       ; This is the interesting bit.  This array length is needed for an OpAccessChain
       ; computing %ac, but the algorithm had to track back through %ac_rtarr's
       ; definition to find the base pointer %ac_ssbo.
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %ac_ssbo 2
       ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %int %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %j %int_0 %[[smin]]
       ; CHECK: %[[clamp_k:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %k %int_0 %int_3
       ; CHECK: %ac = )" << ac
            << R"( %ptr_ty %ac_rtarr %[[clamp_j]] %int_1 %[[clamp_k]]
       ; CHECK-NOT: AccessChain
       )" << MainPrefix()
            << "%ac_ssbo = " << ac << " %ssbo_pty %var %i\n"
            << "%ac_rtarr = " << ac << " %rtarr_pty %ac_ssbo %int_2\n"
            << "%ac = " << ac << " %ptr_ty %ac_rtarr %j %int_1 %k\n"

            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest,
       ACSplitACArrayRTArrayStructVectorElemAcrossBasicBlocks) {
  // Split the address calculation across two access chains.  Force
  // the transform to walk up the access chains to find the base variable.
  // This time, put the different access chains in different basic blocks.
  // This is an integrity check to ensure that we keep the instruction-to-block
  // mapping consistent.
  for (auto* ac : AccessChains()) {
    std::ostringstream shaders;
    shaders << ShaderPreambleAC({"i", "j", "k", "bb1", "bb2", "ssbo_s",
                                 "ssbo_pty", "rtarr_pty", "ac_ssbo",
                                 "ac_rtarr"})
            << "OpDecorate %rtarr ArrayStride 32\n"
            << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n"
            << "OpMemberDecorate %rtelem 1 Offset 16\n"
            << TypesVoid() << TypesInt() << TypesFloat() << R"(
       %v4float = OpTypeVector %float 4
       %rtelem = OpTypeStruct %v4float %v4float
       %rtarr = OpTypeRuntimeArray %rtelem
       %ssbo_s = OpTypeStruct %int %int %rtarr
       %arr_size = OpConstant %int 10
       %arr_ssbo = OpTypeArray %ssbo_s %arr_size
       %var_ty = OpTypePointer Uniform %arr_ssbo
       %ssbo_pty = OpTypePointer Uniform %ssbo_s
       %rtarr_pty = OpTypePointer Uniform %rtarr
       %ptr_ty = OpTypePointer Uniform %float
       %var = OpVariable %var_ty Uniform
       %int_1 = OpConstant %int 1
       %int_2 = OpConstant %int 2
       %i = OpUndef %int
       %j = OpUndef %int
       %k = OpUndef %int
       ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
       ; CHECK-DAG: %int_0 = OpConstant %int 0
       ; CHECK-DAG: %int_9 = OpConstant %int 9
       ; CHECK-DAG: %int_3 = OpConstant %int 3
       ; CHECK-DAG: %[[intmax:\w+]] = OpConstant %int 2147483647
       ; CHECK: OpLabel
       ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %i %int_0 %int_9
       ; CHECK: %ac_ssbo = )" << ac
            << R"( %ssbo_pty %var %[[clamp_i]]
       ; CHECK: %bb1 = OpLabel
       ; CHECK: %ac_rtarr = )"
            << ac << R"( %rtarr_pty %ac_ssbo %int_2
       ; CHECK: %bb2 = OpLabel

       ; This is the interesting bit.  This array length is needed for an OpAccessChain
       ; computing %ac, but the algorithm had to track back through %ac_rtarr's
       ; definition to find the base pointer %ac_ssbo.
       ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %ac_ssbo 2
       ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
       ; CHECK: %[[smin:\w+]] = OpExtInst %int %[[GLSLSTD450]] UMin %[[max]] %[[intmax]]
       ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %j %int_0 %[[smin]]
       ; CHECK: %[[clamp_k:\w+]] = OpExtInst %int %[[GLSLSTD450]] SClamp %k %int_0 %int_3
       ; CHECK: %ac = )" << ac
            << R"( %ptr_ty %ac_rtarr %[[clamp_j]] %int_1 %[[clamp_k]]
       ; CHECK-NOT: AccessChain
       )" << MainPrefix()
            << "%ac_ssbo = " << ac << " %ssbo_pty %var %i\n"
            << "OpBranch %bb1\n%bb1 = OpLabel\n"
            << "%ac_rtarr = " << ac << " %rtarr_pty %ac_ssbo %int_2\n"
            << "OpBranch %bb2\n%bb2 = OpLabel\n"
            << "%ac = " << ac << " %ptr_ty %ac_rtarr %j %int_1 %k\n"

            << MainSuffix();
    SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
  }
}

TEST_F(GraphicsRobustAccessTest, bug3813) {
  // This shader comes from Dawn's
  // TextureViewSamplingTest.TextureCubeMapOnWholeTexture, converted from GLSL
  // by glslang.
  // The pass was inserting a signed 32-bit int type, but not correctly marking
  // the shader as changed.
  std::string shader = R"(
; SPIR-V
; Version: 1.0
; Generator: Google Shaderc over Glslang; 10
; Bound: 46
; Schema: 0
       OpCapability Shader
  %1 = OpExtInstImport "GLSL.std.450"
       OpMemoryModel Logical GLSL450
       OpEntryPoint Fragment %4 "main" %12 %29
       OpExecutionMode %4 OriginUpperLeft
       OpSource GLSL 450
       OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
       OpSourceExtension "GL_GOOGLE_include_directive"
       OpName %4 "main"
       OpName %8 "sc"
       OpName %12 "texCoord"
       OpName %21 "tc"
       OpName %29 "fragColor"
       OpName %32 "texture0"
       OpName %36 "sampler0"
       OpDecorate %12 Location 0
       OpDecorate %29 Location 0
       OpDecorate %32 DescriptorSet 0
       OpDecorate %32 Binding 1
       OpDecorate %36 DescriptorSet 0
       OpDecorate %36 Binding 0
  %2 = OpTypeVoid
  %3 = OpTypeFunction %2
  %6 = OpTypeFloat 32
  %7 = OpTypePointer Function %6
  %9 = OpConstant %6 2
 %10 = OpTypeVector %6 2
 %11 = OpTypePointer Input %10
 %12 = OpVariable %11 Input
 %13 = OpTypeInt 32 0
 %14 = OpConstant %13 0
 %15 = OpTypePointer Input %6
 %19 = OpConstant %6 1
 %22 = OpConstant %13 1
 %27 = OpTypeVector %6 4
 %28 = OpTypePointer Output %27
 %29 = OpVariable %28 Output
 %30 = OpTypeImage %6 Cube 0 0 0 1 Unknown
 %31 = OpTypePointer UniformConstant %30
 %32 = OpVariable %31 UniformConstant
 %34 = OpTypeSampler
 %35 = OpTypePointer UniformConstant %34
 %36 = OpVariable %35 UniformConstant
 %38 = OpTypeSampledImage %30
 %43 = OpTypeVector %6 3
  %4 = OpFunction %2 None %3
  %5 = OpLabel
  %8 = OpVariable %7 Function
 %21 = OpVariable %7 Function
 %16 = OpAccessChain %15 %12 %14
 %17 = OpLoad %6 %16
 %18 = OpFMul %6 %9 %17
 %20 = OpFSub %6 %18 %19
       OpStore %8 %20
 %23 = OpAccessChain %15 %12 %22
 %24 = OpLoad %6 %23
 %25 = OpFMul %6 %9 %24
 %26 = OpFSub %6 %25 %19
       OpStore %21 %26
 %33 = OpLoad %30 %32
 %37 = OpLoad %34 %36
 %39 = OpSampledImage %38 %33 %37
 %40 = OpLoad %6 %21
 %41 = OpLoad %6 %8
 %42 = OpFNegate %6 %41
 %44 = OpCompositeConstruct %43 %19 %40 %42
 %45 = OpImageSampleImplicitLod %27 %39 %44
       OpStore %29 %45
       OpReturn
       OpFunctionEnd
)";

  std::string expected = R"(OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %texCoord %fragColor
OpExecutionMode %main OriginUpperLeft
OpSource GLSL 450
OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
OpSourceExtension "GL_GOOGLE_include_directive"
OpName %main "main"
OpName %sc "sc"
OpName %texCoord "texCoord"
OpName %tc "tc"
OpName %fragColor "fragColor"
OpName %texture0 "texture0"
OpName %sampler0 "sampler0"
OpDecorate %texCoord Location 0
OpDecorate %fragColor Location 0
OpDecorate %texture0 DescriptorSet 0
OpDecorate %texture0 Binding 1
OpDecorate %sampler0 DescriptorSet 0
OpDecorate %sampler0 Binding 0
%void = OpTypeVoid
%10 = OpTypeFunction %void
%float = OpTypeFloat 32
%_ptr_Function_float = OpTypePointer Function %float
%float_2 = OpConstant %float 2
%v2float = OpTypeVector %float 2
%_ptr_Input_v2float = OpTypePointer Input %v2float
%texCoord = OpVariable %_ptr_Input_v2float Input
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%_ptr_Input_float = OpTypePointer Input %float
%float_1 = OpConstant %float 1
%uint_1 = OpConstant %uint 1
%v4float = OpTypeVector %float 4
%_ptr_Output_v4float = OpTypePointer Output %v4float
%fragColor = OpVariable %_ptr_Output_v4float Output
%23 = OpTypeImage %float Cube 0 0 0 1 Unknown
%_ptr_UniformConstant_23 = OpTypePointer UniformConstant %23
%texture0 = OpVariable %_ptr_UniformConstant_23 UniformConstant
%25 = OpTypeSampler
%_ptr_UniformConstant_25 = OpTypePointer UniformConstant %25
%sampler0 = OpVariable %_ptr_UniformConstant_25 UniformConstant
%27 = OpTypeSampledImage %23
%v3float = OpTypeVector %float 3
%int = OpTypeInt 32 1
%main = OpFunction %void None %10
%29 = OpLabel
%sc = OpVariable %_ptr_Function_float Function
%tc = OpVariable %_ptr_Function_float Function
%30 = OpAccessChain %_ptr_Input_float %texCoord %uint_0
%31 = OpLoad %float %30
%32 = OpFMul %float %float_2 %31
%33 = OpFSub %float %32 %float_1
OpStore %sc %33
%34 = OpAccessChain %_ptr_Input_float %texCoord %uint_1
%35 = OpLoad %float %34
%36 = OpFMul %float %float_2 %35
%37 = OpFSub %float %36 %float_1
OpStore %tc %37
%38 = OpLoad %23 %texture0
%39 = OpLoad %25 %sampler0
%40 = OpSampledImage %27 %38 %39
%41 = OpLoad %float %tc
%42 = OpLoad %float %sc
%43 = OpFNegate %float %42
%44 = OpCompositeConstruct %v3float %float_1 %41 %43
%45 = OpImageSampleImplicitLod %v4float %40 %44
OpStore %fragColor %45
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<GraphicsRobustAccessPass>(shader, expected, false,
                                                  true);
}

TEST_F(GraphicsRobustAccessTest, ReplaceIndexReportsChanged) {
  // A ClusterFuzz generated shader that triggered a
  // "Binary size unexpectedly changed despite the optimizer saying there was no
  // change" assertion.
  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/4166.
  std::string shader = R"(
; SPIR-V
; Version: 1.0
; Generator: Google Shaderc over Glslang; 245
; Bound: 41
; Schema: 0
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %main "else" %gl_GlobalInvocationID
               OpExecutionMode %main LocalSize 1 1 3338665985
               OpSource GLSL 450
               OpSourceExtension "GL_GOOGLE_cpp_style_line_directive"
               OpSourceExtension "GL_GOOGLE_include_directive"
               OpName %main "main"
               OpName %index "index"
               OpName %gl_GlobalInvocationID "gl_GlobalInvocationID"
               OpName %S "S"
               OpMemberName %_struct_24 0 ""
               OpMemberName %_struct_24 1 ""
               OpName %Dst "Dst"
               OpMemberName %Dst 0 "s"
               OpName %dst "dst"
               OpName %Src "Src"
               OpMemberName %Src 0 "s"
               OpName %src "src"
               OpDecorate %gl_GlobalInvocationID BuiltIn GlobalInvocationId
               OpMemberDecorate %_struct_24 0 Offset 64
               OpMemberDecorate %_struct_24 1 Offset 8
               OpDecorate %_arr__struct_24_uint_1 ArrayStride 16
               OpMemberDecorate %Dst 0 Offset 0
               OpDecorate %Dst BufferBlock
               OpDecorate %dst DescriptorSet 0
               OpDecorate %dst Binding 1
               OpDecorate %_arr__struct_24_uint_1_0 ArrayStride 16
               OpMemberDecorate %Src 0 Offset 0
               OpDecorate %Src Block
               OpDecorate %src DescriptorSet 0
               OpDecorate %src Binding 0
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
       %uint = OpTypeInt 32 0
%_ptr_Function_uint = OpTypePointer Function %uint
     %v3uint = OpTypeVector %uint 3
%_ptr_Input_v3uint = OpTypePointer Input %v3uint
%gl_GlobalInvocationID = OpVariable %_ptr_Input_v3uint Input
  %uint_4864 = OpConstant %uint 4864
%_ptr_Input_uint = OpTypePointer Input %uint
     %uint_1 = OpConstant %uint 1
       %bool = OpTypeBool
     %v2uint = OpTypeVector %uint 2
 %_struct_24 = OpTypeStruct %_ptr_Input_uint %v2uint
%_arr__struct_24_uint_1 = OpTypeArray %_struct_24 %uint_1
        %Dst = OpTypeStruct %_arr__struct_24_uint_1
%_ptr_Uniform_Dst = OpTypePointer Uniform %Dst
        %dst = OpVariable %_ptr_Uniform_Dst Uniform
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_arr__struct_24_uint_1_0 = OpTypeArray %_struct_24 %uint_1
        %Src = OpTypeStruct %_arr__struct_24_uint_1_0
%_ptr_Uniform_Src = OpTypePointer Uniform %Src
        %src = OpVariable %_ptr_Uniform_Src Uniform
%_ptr_Uniform__struct_24 = OpTypePointer Uniform %_struct_24
       %main = OpFunction %void None %3
          %5 = OpLabel
      %index = OpVariable %_ptr_Function_uint Function
         %14 = OpAccessChain %_ptr_Input_uint %gl_GlobalInvocationID %uint_4864
         %15 = OpLoad %uint %14
               OpStore %index %15
         %16 = OpLoad %uint %index
          %S = OpUGreaterThanEqual %bool %16 %uint_1
               OpSelectionMerge %21 None
               OpBranchConditional %S %20 %21
         %20 = OpLabel
               OpReturn
         %21 = OpLabel
         %31 = OpLoad %uint %index
         %36 = OpLoad %uint %index
         %38 = OpAccessChain %_ptr_Uniform__struct_24 %src %int_0 %36
         %39 = OpLoad %_struct_24 %38
         %40 = OpAccessChain %_ptr_Uniform__struct_24 %dst %int_0 %31
               OpStore %40 %39
               OpReturn
               OpFunctionEnd
)";

  std::vector<uint32_t> optimized_bin;
  auto status = spvtools::opt::Pass::Status::Failure;
  std::tie(optimized_bin, status) =
      SinglePassRunToBinary<GraphicsRobustAccessPass>(shader, false);
  // Check whether the pass returns the correct modification indication.
  EXPECT_EQ(status, spvtools::opt::Pass::Status::SuccessWithChange);
}

// TODO(dneto): Test access chain index wider than 64 bits?
// TODO(dneto): Test struct access chain index wider than 64 bits?
// TODO(dneto): OpImageTexelPointer
//   - all Dim types: 1D 2D Cube 3D Rect Buffer
//   - all Dim types that can be arrayed: 1D 2D 3D
//   - sample index: set to 0 if not multisampled
//   - Dim (2D, Cube Rect} with multisampling
//      -1 0 max excess
// TODO(dneto): Test OpImageTexelPointer with coordinate component index other
// than 32 bits.

}  // namespace
