// Copyright (c) 2023-2025 Arm Ltd.
//
// 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 "gmock/gmock.h"
#include "test/unit_spirv.h"
#include "test/val/val_fixtures.h"

namespace spvtools {
namespace val {
namespace {

using ::testing::ContainsRegex;
using ::testing::HasSubstr;

using ValidateTensor = spvtest::ValidateBase<std::string>;

constexpr spv_target_env SPVENV = SPV_ENV_VULKAN_1_3;

std::string GenerateModule(const std::string& body) {
  const std::string header = R"(
                     OpCapability Shader
                     OpCapability VulkanMemoryModel
                     OpCapability Int8
                     OpCapability TensorsARM
                     OpExtension "SPV_ARM_tensors"
                     OpMemoryModel Logical Vulkan
                     OpEntryPoint GLCompute %fnep "main"
                     OpExecutionMode %fnep LocalSize 1 1 1
                     OpDecorate %tensor_var DescriptorSet 0
                     OpDecorate %tensor_var Binding 0
                     OpDecorate %tensor_var_float_unranked DescriptorSet 0
                     OpDecorate %tensor_var_float_unranked Binding 1
                     OpDecorate %tensor_var_spec_rank DescriptorSet 0
                     OpDecorate %tensor_var_spec_rank Binding 2
             %void = OpTypeVoid
             %uint = OpTypeInt 32 0
            %float = OpTypeFloat 32
             %fnty = OpTypeFunction %void
        %uint_vec4 = OpTypeVector %uint 4
           %uint_0 = OpConstant %uint 0
      %uint_0_spec = OpSpecConstant %uint 0
           %uint_1 = OpConstant %uint 1
           %uint_2 = OpConstant %uint 2
           %uint_3 = OpConstant %uint 3
           %uint_4 = OpConstant %uint 4
          %uint_42 = OpConstant %uint 42
       %uint_1_bis = OpConstant %uint 1
          %float_1 = OpConstant %float 1
%uint_vec4_1_1_1_1 = OpConstantComposite %uint_vec4 %uint_1 %uint_1 %uint_1 %uint_1
        %uint_arr1 = OpTypeArray %uint %uint_1
        %uint_arr2 = OpTypeArray %uint %uint_2
        %uint_arr3 = OpTypeArray %uint %uint_3
        %uint_arr4 = OpTypeArray %uint %uint_4
       %float_arr4 = OpTypeArray %float %uint_4
%uint_arr4_1_1_1_1 = OpConstantComposite %uint_arr4 %uint_1 %uint_1 %uint_1 %uint_1
%uint_arr4_0_0_0_0_spec = OpSpecConstantComposite %uint_arr4 %uint_0_spec %uint_0_spec %uint_0_spec %uint_0_spec
    %uint_arr2_1_1 = OpConstantComposite %uint_arr2 %uint_1 %uint_1
%float_arr4_1_1_1_1 = OpConstantComposite %float_arr4 %float_1 %float_1 %float_1 %float_1
%uint_arr4_1_1_0_1 = OpConstantComposite %uint_arr4 %uint_1 %uint_1 %uint_0 %uint_1
 %uint_ptr_Private = OpTypePointer Private %uint
%uint_arr4_ptr_Private = OpTypePointer Private %uint_arr4
       %uint_var_1 = OpVariable %uint_ptr_Private Private %uint_1
%var_uint_arr4_1_1_1_1 = OpVariable %uint_arr4_ptr_Private Private %uint_arr4_1_1_1_1
    %tensor_uint_4 = OpTypeTensorARM %uint %uint_4
 %tensor_uint_spec = OpTypeTensorARM %uint %uint_0_spec
     %tensor_float = OpTypeTensorARM %float
%tensor_uint_4_ptr_UniformConstant = OpTypePointer UniformConstant %tensor_uint_4
%tensor_var = OpVariable %tensor_uint_4_ptr_UniformConstant UniformConstant
%tensor_float_ptr_UniformConstant = OpTypePointer UniformConstant %tensor_float
%tensor_var_float_unranked = OpVariable %tensor_float_ptr_UniformConstant UniformConstant
%tensor_uint_spec_ptr_UniformConstant = OpTypePointer UniformConstant %tensor_uint_spec
%tensor_var_spec_rank = OpVariable %tensor_uint_spec_ptr_UniformConstant UniformConstant
)";
  const std::string footer = R"(
             %fnep = OpFunction %void None %fnty
            %label = OpLabel
                     OpReturn
                     OpFunctionEnd
)";
  return header + body + footer;
}

//
// Type tests
//

TEST_F(ValidateTensor, ValidTypeElementTypeOnly) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTypeTensorElementTypeNotScalar) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %void
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpTypeTensorARM Element Type <id> "
                            "'.*' is not a scalar type.*"));
}

TEST_F(ValidateTensor, InvalidTypeDuplicateElementTypeTensorType) {
  const std::string src = R"(
    %typeA = OpTypeTensorARM %uint
    %typeB = OpTypeTensorARM %uint
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Duplicate non-aggregate type declarations are not allowed."));
}

TEST_F(ValidateTensor, ValidTypeElementTypeAndRank) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTypeElementTypeAndRankUsingSpecConstant) {
  const std::string src = R"(
    %rank_spec = OpSpecConstant %uint  0
    %test_type = OpTypeTensorARM %uint %rank_spec
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTypeDuplicateRankedTensorType) {
  const std::string src = R"(
    %typeA = OpTypeTensorARM %uint %uint_1
    %typeB = OpTypeTensorARM %uint %uint_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Duplicate non-aggregate type declarations are not allowed."));
}

TEST_F(ValidateTensor, ValidTypeEquivalentRankedTensorType) {
  const std::string src = R"(
    %typeA = OpTypeTensorARM %uint %uint_1
    %typeB = OpTypeTensorARM %uint %uint_1_bis
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTypeRankNotConstant) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_var_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpTypeTensorARM Rank <id> '.*' "
                            "is not a constant instruction.*"));
}

TEST_F(ValidateTensor, InvalidTypeRankConstantButNotIntegerType) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %float_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpTypeTensorARM Rank <id> '.*' "
                            "does not have a scalar integer type.*"));
}

TEST_F(ValidateTensor, InvalidTypeRankConstantIntegerTypeButNotScalar) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_vec4_1_1_1_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpTypeTensorARM Rank <id> '.*' "
                            "does not have a scalar integer type.*"));
}

TEST_F(ValidateTensor, InvalidTypeRank0) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_0
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpTypeTensorARM Rank <id> '.*' must "
                            "define a value greater than 0.*"));
}

TEST_F(ValidateTensor, ValidTypeElementTypeAndRankAndShape) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_4 %uint_arr4_1_1_1_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTypeElementTypeAndRankAndShapeUsingSpecConstant) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_0_spec %uint_arr4_0_0_0_0_spec
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTypeShapeNotConstant) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_4 %var_uint_arr4_1_1_1_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpTypeTensorARM Shape <id> '.*' is not "
                            "a constant instruction.*"));
}

TEST_F(ValidateTensor, InvalidTypeShapeConstantButNotArrayOfInteger) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_4 %uint_vec4_1_1_1_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex(
                  "OpTypeTensorARM Shape <id> '.*' is not "
                  "an array of integer type whose Length is equal to Rank.*"));
}

TEST_F(ValidateTensor, InvalidTypeShapeConstantArrayOfIntegerWrongLength) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_1 %uint_arr4_1_1_1_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex(
                  "OpTypeTensorARM Shape <id> '.*' is not "
                  "an array of integer type whose Length is equal to Rank.*"));
}

TEST_F(ValidateTensor,
       InvalidTypeShapeConstantArrayOfIntegerWithZeroConstituent) {
  const std::string src = R"(
    %test_type = OpTypeTensorARM %uint %uint_4 %uint_arr4_1_1_0_1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      ContainsRegex(
          "OpTypeTensorARM Shape constituent 2 is not greater than 0.*"));
}

//
// Constants tests
//
TEST_F(ValidateTensor, ValidNullTensorConstantRank1) {
  const std::string src = R"(
   %uint_arr1_4 = OpConstantComposite %uint_arr1 %uint_4
    %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_4
           %cst = OpConstantNull %ts_uint_r1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidNullTensorConstantRank4) {
  const std::string src = R"(
%uint_arr4_2_2_2_2 = OpConstantComposite %uint_arr4 %uint_2 %uint_2 %uint_2 %uint_2
       %ts_uint_r4 = OpTypeTensorARM %uint %uint_4 %uint_arr4_2_2_2_2
              %cst = OpConstantNull %ts_uint_r4
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidNullTensorConstantRankedButNotShaped) {
  const std::string src = R"(
       %ts_uint_r1 = OpTypeTensorARM %uint %uint_1
              %cst = OpConstantNull %ts_uint_r1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpConstantNull Result Type <id> '.*' "
                            "cannot have a null value.*"));
}

TEST_F(ValidateTensor, InvalidNullTensorConstantNotRanked) {
  const std::string src = R"(
       %ts_uint_r1 = OpTypeTensorARM %uint
              %cst = OpConstantNull %ts_uint_r1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpConstantNull Result Type <id> '.*' "
                            "cannot have a null value.*"));
}

TEST_F(ValidateTensor, ValidTensorConstantRank1) {
  const std::string src = R"(
   %uint_arr1_4 = OpConstantComposite %uint_arr1 %uint_4
    %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_4
           %cst = OpConstantComposite %ts_uint_r1 %uint_42 %uint_42 %uint_42 %uint_42
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorConstantRank1SpecConstant) {
  const std::string src = R"(
   %uint_arr1_4 = OpSpecConstantComposite %uint_arr1 %uint_0_spec
    %ts_uint_r1 = OpTypeTensorARM %uint %uint_0_spec %uint_arr1_4
           %cst = OpSpecConstantComposite %ts_uint_r1 %uint_0_spec %uint_0_spec %uint_0_spec %uint_0_spec
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTensorConstantRank1NotEnoughConstituents) {
  const std::string src = R"(
   %uint_arr1_4 = OpConstantComposite %uint_arr1 %uint_4
    %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_4
           %cst = OpConstantComposite %ts_uint_r1 %uint_42 %uint_42 %uint_42
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      ContainsRegex("OpConstantComposite Constituent count does not match the "
                    "shape of Result Type <id> '.*' "
                    "along its outermost dimension, expected 4 but got 3.*"));
}

TEST_F(ValidateTensor, InvalidTensorConstantRank1TooManyConstituents) {
  const std::string src = R"(
   %uint_arr1_4 = OpConstantComposite %uint_arr1 %uint_4
    %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_4
           %cst = OpConstantComposite %ts_uint_r1 %uint_42 %uint_42 %uint_42 %uint_42 %uint_42
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      ContainsRegex("OpConstantComposite Constituent count does not match the "
                    "shape of Result Type <id> '.*' "
                    "along its outermost dimension, expected 4 but got 5.*"));
}

TEST_F(ValidateTensor,
       InvalidTensorConstantRank1ConstituentNotConstantOrUndef) {
  const std::string src = R"(
   %uint_arr1_4 = OpConstantComposite %uint_arr1 %uint_4
    %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_4
           %cst = OpConstantComposite %ts_uint_r1 %tensor_var %tensor_var %tensor_var %tensor_var
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpConstantComposite Constituent <id> "
                            "'.*' is not a constant or undef.*"));
}

TEST_F(ValidateTensor, InvalidTensorConstantRank1WrongConstituentType) {
  const std::string src = R"(
   %uint_arr1_4 = OpConstantComposite %uint_arr1 %uint_4
    %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_4
           %cst = OpConstantComposite %ts_uint_r1 %uint_42 %uint_42 %float_1 %uint_42
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      ContainsRegex("OpConstantComposite Constituent <id> '.*' "
                    "type does not match the element type of the tensor "
                    ".*"));
}

TEST_F(ValidateTensor, ValidTensorConstantRank4) {
  const std::string src = R"(
      %uint_arr1_2 = OpConstantComposite %uint_arr1 %uint_2
    %uint_arr2_2_2 = OpConstantComposite %uint_arr2 %uint_2 %uint_2
  %uint_arr3_2_2_2 = OpConstantComposite %uint_arr3 %uint_2 %uint_2 %uint_2
%uint_arr4_2_2_2_2 = OpConstantComposite %uint_arr4 %uint_2 %uint_2 %uint_2 %uint_2
       %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_2
       %ts_uint_r2 = OpTypeTensorARM %uint %uint_2 %uint_arr2_2_2
       %ts_uint_r3 = OpTypeTensorARM %uint %uint_3 %uint_arr3_2_2_2
       %ts_uint_r4 = OpTypeTensorARM %uint %uint_4 %uint_arr4_2_2_2_2
           %cst_r1 = OpConstantComposite %ts_uint_r1 %uint_42 %uint_42
           %cst_r2 = OpConstantComposite %ts_uint_r2 %cst_r1 %cst_r1
           %cst_r3 = OpConstantComposite %ts_uint_r3 %cst_r2 %cst_r2
           %cst_r4 = OpConstantComposite %ts_uint_r4 %cst_r3 %cst_r3
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorConstantRank2SpecConstantConstituent) {
  const std::string src = R"(
      %uint_arr1_2 = OpSpecConstantComposite %uint_arr1 %uint_0_spec
    %uint_arr2_2_2 = OpSpecConstantComposite %uint_arr2 %uint_0_spec %uint_0_spec
       %ts_uint_r1 = OpTypeTensorARM %uint %uint_0_spec %uint_arr1_2
       %ts_uint_r2 = OpTypeTensorARM %uint %uint_2 %uint_arr2_2_2
           %cst_r1 = OpSpecConstantComposite %ts_uint_r1 %uint_0_spec %uint_0_spec
           %cst_r2 = OpSpecConstantComposite %ts_uint_r2 %cst_r1 %cst_r1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTensorConstantRank2ConstituentNotATensor) {
  const std::string src = R"(
      %uint_arr1_2 = OpConstantComposite %uint_arr1 %uint_2
    %uint_arr2_2_2 = OpConstantComposite %uint_arr2 %uint_2 %uint_2
       %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_2
       %ts_uint_r2 = OpTypeTensorARM %uint %uint_2 %uint_arr2_2_2
           %cst_r1 = OpConstantComposite %ts_uint_r1 %uint_42 %uint_42
           %cst_r2 = OpConstantComposite %ts_uint_r2 %uint_1 %cst_r1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpConstantComposite Constituent <id> "
                            "'.*' must be an OpTypeTensorARM.*"));
}

TEST_F(ValidateTensor,
       InvalidTensorConstantRank2ConstituentTensorWrongElementType) {
  const std::string src = R"(
      %uint_arr1_2 = OpConstantComposite %uint_arr1 %uint_2
    %uint_arr2_2_2 = OpConstantComposite %uint_arr2 %uint_2 %uint_2
       %ts_float_r1 = OpTypeTensorARM %float %uint_1 %uint_arr1_2
       %ts_uint_r2 = OpTypeTensorARM %uint %uint_2 %uint_arr2_2_2
           %cst_r1 = OpConstantComposite %ts_float_r1 %float_1 %float_1
           %cst_r2 = OpConstantComposite %ts_uint_r2 %cst_r1 %cst_r1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpConstantComposite Constituent <id> "
                            "'.*' must have the same Element Type "
                            "as Result Type <id> .*"));
}

TEST_F(ValidateTensor, InvalidTensorConstantRank2ConstituentTensorWrongRank) {
  const std::string src = R"(
      %uint_arr1_2 = OpConstantComposite %uint_arr1 %uint_2
    %uint_arr2_2_2 = OpConstantComposite %uint_arr2 %uint_2 %uint_2
       %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_2
       %ts_uint_r2 = OpTypeTensorARM %uint %uint_2 %uint_arr2_2_2
           %cst_r1 = OpConstantComposite %ts_uint_r1 %uint_42 %uint_42
           %cst_r2 = OpConstantComposite %ts_uint_r2 %cst_r1 %cst_r1
              %cst = OpConstantComposite %ts_uint_r2 %cst_r1 %cst_r2
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              ContainsRegex("OpConstantComposite Constituent <id> "
                            "'.*' must have a Rank that is "
                            "1 less than the Rank of Result Type <id> "
                            "'.*', expected 1 but got 2.*"));
}

TEST_F(ValidateTensor, InvalidTensorConstantRank2ConstituentTensorWrongShape) {
  const std::string src = R"(
      %uint_arr1_3 = OpConstantComposite %uint_arr1 %uint_3
    %uint_arr2_2_2 = OpConstantComposite %uint_arr2 %uint_2 %uint_2
       %ts_uint_r1 = OpTypeTensorARM %uint %uint_1 %uint_arr1_3
       %ts_uint_r2 = OpTypeTensorARM %uint %uint_2 %uint_arr2_2_2
           %cst_r1 = OpConstantComposite %ts_uint_r1 %uint_42 %uint_42 %uint_42
           %cst_r2 = OpConstantComposite %ts_uint_r2 %cst_r1 %cst_r1
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      ContainsRegex(
          "OpConstantComposite Constituent <id> "
          "'.*' must have a Shape that matches "
          "that of Result Type <id> '.*' along "
          "all inner dimensions of Result Type, expected 2 for dimension 0 of "
          "Constituent but got 3.*"));
}

//
// Read tests
//

TEST_F(ValidateTensor, ValidTensorReadScalar) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorReadArray) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint_arr2 %tensor %uint_arr4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorReadSpecConstantRank) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_spec %tensor_var_spec_rank
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTensorReadResultTypeVoid) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %void %tensor %uint_arr4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Result Type to be a scalar type or array of scalar type"));
}

TEST_F(ValidateTensor, InvalidTensorReadResultTypeVector) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint_vec4 %tensor %uint_arr4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Result Type to be a scalar type or array of scalar type"));
}

TEST_F(ValidateTensor,
       InvalidTensorReadResultTypeDoesNotMatchTensorElementType) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %float %tensor %uint_arr4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Result Type to be the same as the Element Type of Tensor"));
}

TEST_F(ValidateTensor, InvalidTensorReadTensorNotRanked) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_float %tensor_var_float_unranked
            %val = OpTensorReadARM %float %tensor %uint_arr4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Tensor to be an OpTypeTensorARM whose Rank is specified"));
}

TEST_F(ValidateTensor, InvalidTensorReadCoordinatesNotArray) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Coordinates to be an array whose Element Type is an "
          "integer type and whose Length is equal to the Rank of Tensor."));
}

TEST_F(ValidateTensor, InvalidTensorReadCoordinatesArrayNotInteger) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %float_arr4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Coordinates to be an array whose Element Type is an "
          "integer type and whose Length is equal to the Rank of Tensor."));
}

TEST_F(ValidateTensor, InvalidTensorReadCoordinatesArrayIntegerWrongLength) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr2_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Coordinates to be an array whose Element Type is an "
          "integer type and whose Length is equal to the Rank of Tensor."));
}

TEST_F(ValidateTensor, ValidTensorReadScalarWithNoneTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1 NoneARM
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorReadScalarWithNontemporalTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1 NontemporalARM
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorReadScalarOutOfBoundsValueTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1 OutOfBoundsValueARM %uint_42
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor,
       InvalidTensorReadScalarOutOfBoundsValueTensorOperandsValueWrongType) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1 OutOfBoundsValueARM %float_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected the type of the OutOfBoundsValueARM value to be the same "
          "as the Element Type of Tensor."));
}

TEST_F(ValidateTensor,
       InvalidTensorReadScalarMakeElementAvailableTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1 MakeElementAvailableARM %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "MakeElementAvailableARM cannot be used with OpTensorReadARM."));
}

TEST_F(ValidateTensor,
       ValidTensorReadScalarWithMakeElementVisibleTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1 MakeElementVisibleARM|NonPrivateElementARM %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(
    ValidateTensor,
    InvalidTensorReadScalarMakeElementVisibleTensorOperandsWithoutNonPrivateElement) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
            %val = OpTensorReadARM %uint %tensor %uint_arr4_1_1_1_1 MakeElementVisibleARM %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("MakeElementAvailableARM requires NonPrivateElementARM"));
}

//
// Write tests
//

TEST_F(ValidateTensor, ValidTensorWriteScalar) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorWriteArray) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_arr2_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorWriteSpecConstantRank) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_spec %tensor_var_spec_rank
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTensorWriteObjectNotScalarOrArrayOfScalar) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_vec4_1_1_1_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Expected Object to be a scalar type or array of scalar type "
                "that is the same as the Element Type of Tensor."));
}

TEST_F(ValidateTensor, InvalidTensorWriteObjectDoesNotMatchTensorElementType) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %float_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Expected Object to be a scalar type or array of scalar type "
                "that is the same as the Element Type of Tensor."));
}

TEST_F(ValidateTensor, InvalidTensorWriteTensorNotRanked) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_float %tensor_var_float_unranked
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %float_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Tensor to be an OpTypeTensorARM whose Rank is specified"));
}

TEST_F(ValidateTensor, InvalidTensorWriteCoordinatesNotArray) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_1 %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Coordinates to be an array whose Element Type is an "
          "integer type and whose Length is equal to the Rank of Tensor."));
}

TEST_F(ValidateTensor, InvalidTensorWriteCoordinatesArrayNotInteger) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %float_arr4_1_1_1_1 %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Coordinates to be an array whose Element Type is an "
          "integer type and whose Length is equal to the Rank of Tensor."));
}

TEST_F(ValidateTensor, InvalidTensorWriteCoordinatesArrayIntegerWrongLength) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr2_1_1 %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Coordinates to be an array whose Element Type is an "
          "integer type and whose Length is equal to the Rank of Tensor."));
}

TEST_F(ValidateTensor, ValidTensorWriteScalarWithNoneTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_1 NoneARM
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorWriteScalarWithNontemporalTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_1 NontemporalARM
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor,
       ValidTensorWriteScalarWithMakeElementAvailableTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_1 MakeElementAvailableARM|NonPrivateElementARM %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor,
       InvalidTensorWriteScalarWithOutOfVoundsValueTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_1 OutOfBoundsValueARM %uint_42
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OutOfBoundsValue Tensor Operand not allowed with "
                        "OpTensorWriteARM."));
}

TEST_F(ValidateTensor,
       InvalidTensorWriteScalarWithMakeElementVisibleTensorOperands) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_1 MakeElementVisibleARM %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("MakeElementVisibleARM not allowed with OpTensorWriteARM."));
}

TEST_F(
    ValidateTensor,
    InvalidTensorWriteScalarWithMakeElementAvailableTensorOperandsWithoutNonPrivateElement) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
                   OpTensorWriteARM %tensor %uint_arr4_1_1_1_1 %uint_1 MakeElementAvailableARM %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("MakeElementAvailableARM requires NonPrivateElementARM."));
}

//
// Query Size tests
//

TEST_F(ValidateTensor, ValidTensorQuerySize) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
           %size = OpTensorQuerySizeARM %uint %tensor %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, ValidTensorQuerySizeSpecConstant) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_spec %tensor_var_spec_rank
           %size = OpTensorQuerySizeARM %uint %tensor %uint_0_spec
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPVENV));
}

TEST_F(ValidateTensor, InvalidTensorQuerySizeTensorNotRanked) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_float %tensor_var_float_unranked
           %size = OpTensorQuerySizeARM %uint %tensor %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Expected Tensor to be an OpTypeTensorARM whose Rank is specified"));
}

TEST_F(ValidateTensor, InvalidTensorQuerySizeResultNotIntegerScalarType) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_float %tensor_var_float_unranked
           %size = OpTensorQuerySizeARM %float %tensor %uint_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Expected Result Type to be an integer type scalar"));
}

TEST_F(ValidateTensor, InvalidTensorQuerySizeDimensionNotIntegerType) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
           %size = OpTensorQuerySizeARM %uint %tensor %float_1
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Dimension must come from a constant instruction of "
                        "scalar integer type."));
}

TEST_F(ValidateTensor, InvalidTensorQuerySizeDimensionNotConstant) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
           %dims = OpLoad %uint_arr4 %var_uint_arr4_1_1_1_1
            %dim = OpCompositeExtract %uint %dims 0
           %size = OpTensorQuerySizeARM %uint %tensor %dim
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Dimension must come from a constant instruction of "
                        "scalar integer type."));
}

TEST_F(ValidateTensor, InvalidTensorQuerySizeDimension) {
  const std::string src = R"(
             %fn = OpFunction %void None %fnty
         %label1 = OpLabel
         %tensor = OpLoad %tensor_uint_4 %tensor_var
           %size = OpTensorQuerySizeARM %uint %tensor %uint_42
                   OpReturn
                   OpFunctionEnd
)";
  std::string spvasm = GenerateModule(src);
  CompileSuccessfully(spvasm, SPVENV);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPVENV));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Dimension (42) must be less than the Rank of Tensor (4)"));
}

}  // namespace
}  // namespace val
}  // namespace spvtools
