// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// 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.

// Assembler tests for instructions in the "Group Instrucions" section of the
// SPIR-V spec.

#include <cstdint>
#include <limits>
#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"

namespace spvtools {
namespace {

using spvtest::Concatenate;
using spvtest::EnumCase;
using spvtest::MakeInstruction;
using ::testing::Eq;

// Test Sampler Addressing Mode enum values

using SamplerAddressingModeTest = spvtest::TextToBinaryTestBase<
    ::testing::TestWithParam<EnumCase<spv::SamplerAddressingMode>>>;

TEST_P(SamplerAddressingModeTest, AnySamplerAddressingMode) {
  const std::string input =
      "%result = OpConstantSampler %type " + GetParam().name() + " 0 Nearest";
  EXPECT_THAT(CompiledInstructions(input),
              Eq(MakeInstruction(spv::Op::OpConstantSampler,
                                 {1, 2, uint32_t(GetParam().value()), 0, 0})));
}

// clang-format off
#define CASE(NAME) { spv::SamplerAddressingMode::NAME, #NAME }
INSTANTIATE_TEST_SUITE_P(
    TextToBinarySamplerAddressingMode, SamplerAddressingModeTest,
    ::testing::ValuesIn(std::vector<EnumCase<spv::SamplerAddressingMode>>{
        CASE(None),
        CASE(ClampToEdge),
        CASE(Clamp),
        CASE(Repeat),
        CASE(RepeatMirrored),
    }));
#undef CASE
// clang-format on

TEST_F(SamplerAddressingModeTest, WrongMode) {
  EXPECT_THAT(CompileFailure("%r = OpConstantSampler %t xxyyzz 0 Nearest"),
              Eq("Invalid sampler addressing mode 'xxyyzz'."));
}

// Test Sampler Filter Mode enum values

using SamplerFilterModeTest = spvtest::TextToBinaryTestBase<
    ::testing::TestWithParam<EnumCase<spv::SamplerFilterMode>>>;

TEST_P(SamplerFilterModeTest, AnySamplerFilterMode) {
  const std::string input =
      "%result = OpConstantSampler %type Clamp 0 " + GetParam().name();
  EXPECT_THAT(CompiledInstructions(input),
              Eq(MakeInstruction(spv::Op::OpConstantSampler,
                                 {1, 2, 2, 0, uint32_t(GetParam().value())})));
}

// clang-format off
#define CASE(NAME) { spv::SamplerFilterMode::NAME, #NAME}
INSTANTIATE_TEST_SUITE_P(
    TextToBinarySamplerFilterMode, SamplerFilterModeTest,
    ::testing::ValuesIn(std::vector<EnumCase<spv::SamplerFilterMode>>{
        CASE(Nearest),
        CASE(Linear),
    }));
#undef CASE
// clang-format on

TEST_F(SamplerFilterModeTest, WrongMode) {
  EXPECT_THAT(CompileFailure("%r = OpConstantSampler %t Clamp 0 xxyyzz"),
              Eq("Invalid sampler filter mode 'xxyyzz'."));
}

struct ConstantTestCase {
  std::string constant_type;
  std::string constant_value;
  std::vector<uint32_t> expected_instructions;
};

using OpConstantValidTest =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<ConstantTestCase>>;

TEST_P(OpConstantValidTest, ValidTypes) {
  const std::string input = "%1 = " + GetParam().constant_type +
                            "\n"
                            "%2 = OpConstant %1 " +
                            GetParam().constant_value + "\n";
  std::vector<uint32_t> instructions;
  EXPECT_THAT(CompiledInstructions(input), Eq(GetParam().expected_instructions))
      << " type: " << GetParam().constant_type
      << " literal: " << GetParam().constant_value;
}

// clang-format off
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryOpConstantValid, OpConstantValidTest,
    ::testing::ValuesIn(std::vector<ConstantTestCase>{
      // Check 16 bits
      {"OpTypeInt 16 0", "0x1234",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x1234})})},
      {"OpTypeInt 16 0", "0x8000",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x8000})})},
      {"OpTypeInt 16 0", "0",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0})})},
      {"OpTypeInt 16 0", "65535",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 65535})})},
      {"OpTypeInt 16 0", "0xffff",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 65535})})},
      {"OpTypeInt 16 1", "0x8000", // Test sign extension.
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0xffff8000})})},
      {"OpTypeInt 16 1", "-32",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, uint32_t(-32)})})},
      {"OpTypeInt 16 1", "0",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0})})},
      {"OpTypeInt 16 1", "-0",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0})})},
      {"OpTypeInt 16 1", "-0x0",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0})})},
      {"OpTypeInt 16 1", "-32768",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, uint32_t(-32768)})})},
      // Check 32 bits
      {"OpTypeInt 32 0", "42",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 42})})},
      {"OpTypeInt 32 1", "-32",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, uint32_t(-32)})})},
      {"OpTypeInt 32 1", "0",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0})})},
      {"OpTypeInt 32 1", "-0",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0})})},
      {"OpTypeInt 32 1", "-0x0",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0})})},
      {"OpTypeInt 32 1", "-0x001",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, uint32_t(-1)})})},
      {"OpTypeInt 32 1", "2147483647",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x7fffffffu})})},
      {"OpTypeInt 32 1", "-2147483648",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x80000000u})})},
      {"OpTypeFloat 32", "1.0",
        Concatenate({MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x3f800000})})},
      {"OpTypeFloat 32", "10.0",
        Concatenate({MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x41200000})})},
      {"OpTypeFloat 32", "-0x1p+128", // -infinity
        Concatenate({MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0xFF800000})})},
      {"OpTypeFloat 32", "0x1p+128", // +infinity
        Concatenate({MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x7F800000})})},
      {"OpTypeFloat 32", "-0x1.8p+128", // A -NaN
        Concatenate({MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0xFFC00000})})},
      {"OpTypeFloat 32", "-0x1.0002p+128", // A +NaN
        Concatenate({MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0xFF800100})})},
      // Check 48 bits
      {"OpTypeInt 48 0", "0x1234",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 48, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x1234, 0})})},
      {"OpTypeInt 48 0", "0x800000000001",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 48, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 1, 0x00008000})})},
      {"OpTypeInt 48 1", "0x800000000000", // Test sign extension.
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 48, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0, 0xffff8000})})},
      {"OpTypeInt 48 1", "-32",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 48, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, uint32_t(-32), uint32_t(-1)})})},
      // Check 64 bits
      {"OpTypeInt 64 0", "0x1234",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x1234, 0})})},
      {"OpTypeInt 64 0", "18446744073709551615",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0xffffffffu, 0xffffffffu})})},
      {"OpTypeInt 64 0", "0xffffffffffffffff",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0xffffffffu, 0xffffffffu})})},
      {"OpTypeInt 64 1", "0x1234",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x1234, 0})})},
      {"OpTypeInt 64 1", "-42",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, uint32_t(-42), uint32_t(-1)})})},
      {"OpTypeInt 64 1", "-0x01",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0xffffffffu, 0xffffffffu})})},
      {"OpTypeInt 64 1", "9223372036854775807",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0xffffffffu, 0x7fffffffu})})},
      {"OpTypeInt 64 1", "0x7fffffff",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 1}),
         MakeInstruction(spv::Op::OpConstant, {1, 2, 0x7fffffffu, 0})})},
    }));
// clang-format on

// A test case for checking OpConstant with invalid literals with a leading
// minus.
struct InvalidLeadingMinusCase {
  std::string type;
  std::string literal;
};

using OpConstantInvalidLeadingMinusTest = spvtest::TextToBinaryTestBase<
    ::testing::TestWithParam<InvalidLeadingMinusCase>>;

TEST_P(OpConstantInvalidLeadingMinusTest, InvalidCase) {
  const std::string input = "%1 = " + GetParam().type +
                            "\n"
                            "%2 = OpConstant %1 " +
                            GetParam().literal;
  EXPECT_THAT(CompileFailure(input),
              Eq("Cannot put a negative number in an unsigned literal"));
}

// clang-format off
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryOpConstantInvalidLeadingMinus, OpConstantInvalidLeadingMinusTest,
    ::testing::ValuesIn(std::vector<InvalidLeadingMinusCase>{
      {"OpTypeInt 16 0", "-0"},
      {"OpTypeInt 16 0", "-0x0"},
      {"OpTypeInt 16 0", "-1"},
      {"OpTypeInt 32 0", "-0"},
      {"OpTypeInt 32 0", "-0x0"},
      {"OpTypeInt 32 0", "-1"},
      {"OpTypeInt 64 0", "-0"},
      {"OpTypeInt 64 0", "-0x0"},
      {"OpTypeInt 64 0", "-1"},
    }));
// clang-format on

// A test case for invalid floating point literals.
struct InvalidFloatConstantCase {
  uint32_t width;
  std::string literal;
};

using OpConstantInvalidFloatConstant = spvtest::TextToBinaryTestBase<
    ::testing::TestWithParam<InvalidFloatConstantCase>>;

TEST_P(OpConstantInvalidFloatConstant, Samples) {
  // Check both kinds of instructions that take literal floats.
  for (const auto& instruction : {"OpConstant", "OpSpecConstant"}) {
    std::stringstream input;
    input << "%1 = OpTypeFloat " << GetParam().width << "\n"
          << "%2 = " << instruction << " %1 " << GetParam().literal;
    std::stringstream expected_error;
    expected_error << "Invalid " << GetParam().width
                   << "-bit float literal: " << GetParam().literal;
    EXPECT_THAT(CompileFailure(input.str()), Eq(expected_error.str()));
  }
}

// clang-format off
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryInvalidFloatConstant, OpConstantInvalidFloatConstant,
    ::testing::ValuesIn(std::vector<InvalidFloatConstantCase>{
        {16, "abc"},
        {16, "--1"},
        {16, "-+1"},
        {16, "+-1"},
        {16, "++1"},
        {16, "1e30"}, // Overflow is an error for 16-bit floats.
        {16, "-1e30"},
        {16, "1e40"},
        {16, "-1e40"},
        {16, "1e400"},
        {16, "-1e400"},
        {32, "abc"},
        {32, "--1"},
        {32, "-+1"},
        {32, "+-1"},
        {32, "++1"},
        {32, "1e40"}, // Overflow is an error for 32-bit floats.
        {32, "-1e40"},
        {32, "1e400"},
        {32, "-1e400"},
        {64, "abc"},
        {64, "--1"},
        {64, "-+1"},
        {64, "+-1"},
        {64, "++1"},
        {32, "1e400"}, // Overflow is an error for 64-bit floats.
        {32, "-1e400"},
    }));
// clang-format on

using OpConstantInvalidTypeTest =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<std::string>>;
TEST_P(OpConstantInvalidTypeTest, InvalidTypes) {
  const std::string input = "%1 = " + GetParam() +
                            "\n"
                            "%2 = OpConstant %1 0\n";
  EXPECT_THAT(
      CompileFailure(input),
      Eq("Type for Constant must be a scalar floating point or integer type"));
}

// clang-format off
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryOpConstantInvalidValidType, OpConstantInvalidTypeTest,
    ::testing::ValuesIn(std::vector<std::string>{
      {"OpTypeVoid",
       "OpTypeBool",
       "OpTypeVector %a 32",
       "OpTypeMatrix %a 32",
       "OpTypeImage %a 1D 0 0 0 0 Unknown",
       "OpTypeSampler",
       "OpTypeSampledImage %a",
       "OpTypeArray %a %b",
       "OpTypeRuntimeArray %a",
       "OpTypeStruct %a",
       "OpTypeOpaque \"Foo\"",
       "OpTypePointer UniformConstant %a",
       "OpTypeFunction %a %b",
       "OpTypeEvent",
       "OpTypeDeviceEvent",
       "OpTypeReserveId",
       "OpTypeQueue",
       "OpTypePipe ReadOnly",

       // Skip OpTypeForwardPointer doesn't even produce a result ID.
       // The assembler errors out if we try to check it in this scenario.

       // Try at least one thing that isn't a type at all
       "OpNot %a %b"
      },
    }));
// clang-format on

using OpSpecConstantValidTest =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<ConstantTestCase>>;

TEST_P(OpSpecConstantValidTest, ValidTypes) {
  const std::string input = "%1 = " + GetParam().constant_type +
                            "\n"
                            "%2 = OpSpecConstant %1 " +
                            GetParam().constant_value + "\n";
  std::vector<uint32_t> instructions;
  EXPECT_THAT(CompiledInstructions(input),
              Eq(GetParam().expected_instructions));
}

// clang-format off
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryOpSpecConstantValid, OpSpecConstantValidTest,
    ::testing::ValuesIn(std::vector<ConstantTestCase>{
      // Check 16 bits
      {"OpTypeInt 16 0", "0x1234",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 0}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0x1234})})},
      {"OpTypeInt 16 0", "0x8000",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 0}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0x8000})})},
      {"OpTypeInt 16 1", "0x8000", // Test sign extension.
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 1}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0xffff8000})})},
      {"OpTypeInt 16 1", "-32",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 16, 1}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, uint32_t(-32)})})},
      // Check 32 bits
      {"OpTypeInt 32 0", "42",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 0}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 42})})},
      {"OpTypeInt 32 1", "-32",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 32, 1}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, uint32_t(-32)})})},
      {"OpTypeFloat 32", "1.0",
        Concatenate({MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0x3f800000})})},
      {"OpTypeFloat 32", "10.0",
        Concatenate({MakeInstruction(spv::Op::OpTypeFloat, {1, 32}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0x41200000})})},
      // Check 48 bits
      {"OpTypeInt 48 0", "0x1234",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 48, 0}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0x1234, 0})})},
      {"OpTypeInt 48 0", "0x800000000001",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 48, 0}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 1, 0x00008000})})},
      {"OpTypeInt 48 1", "0x800000000000", // Test sign extension.
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 48, 1}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0, 0xffff8000})})},
      {"OpTypeInt 48 1", "-32",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 48, 1}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, uint32_t(-32), uint32_t(-1)})})},
      // Check 64 bits
      {"OpTypeInt 64 0", "0x1234",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 0}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0x1234, 0})})},
      {"OpTypeInt 64 1", "0x1234",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 1}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, 0x1234, 0})})},
      {"OpTypeInt 64 1", "-42",
        Concatenate({MakeInstruction(spv::Op::OpTypeInt, {1, 64, 1}),
         MakeInstruction(spv::Op::OpSpecConstant, {1, 2, uint32_t(-42), uint32_t(-1)})})},
    }));
// clang-format on

using OpSpecConstantInvalidTypeTest =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<std::string>>;

TEST_P(OpSpecConstantInvalidTypeTest, InvalidTypes) {
  const std::string input = "%1 = " + GetParam() +
                            "\n"
                            "%2 = OpSpecConstant %1 0\n";
  EXPECT_THAT(CompileFailure(input),
              Eq("Type for SpecConstant must be a scalar floating point or "
                 "integer type"));
}

// clang-format off
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryOpSpecConstantInvalidValidType, OpSpecConstantInvalidTypeTest,
    ::testing::ValuesIn(std::vector<std::string>{
      {"OpTypeVoid",
       "OpTypeBool",
       "OpTypeVector %a 32",
       "OpTypeMatrix %a 32",
       "OpTypeImage %a 1D 0 0 0 0 Unknown",
       "OpTypeSampler",
       "OpTypeSampledImage %a",
       "OpTypeArray %a %b",
       "OpTypeRuntimeArray %a",
       "OpTypeStruct %a",
       "OpTypeOpaque \"Foo\"",
       "OpTypePointer UniformConstant %a",
       "OpTypeFunction %a %b",
       "OpTypeEvent",
       "OpTypeDeviceEvent",
       "OpTypeReserveId",
       "OpTypeQueue",
       "OpTypePipe ReadOnly",

       // Skip testing OpTypeForwardPointer because it doesn't even produce a result ID.

       // Try at least one thing that isn't a type at all
       "OpNot %a %b"
      },
    }));
// clang-format on

const int64_t kMaxUnsigned48Bit = (int64_t(1) << 48) - 1;
const int64_t kMaxSigned48Bit = (int64_t(1) << 47) - 1;
const int64_t kMinSigned48Bit = -kMaxSigned48Bit - 1;

using ConstantRoundTripTest = RoundTripTest;

TEST_P(ConstantRoundTripTest, DisassemblyEqualsAssemblyInput) {
  const std::string assembly = GetParam();
  EXPECT_THAT(EncodeAndDecodeSuccessfully(assembly), Eq(assembly)) << assembly;
}

INSTANTIATE_TEST_SUITE_P(
    OpConstantRoundTrip, ConstantRoundTripTest,
    ::testing::ValuesIn(std::vector<std::string>{
        // 16 bit
        "%1 = OpTypeInt 16 0\n%2 = OpConstant %1 0\n",
        "%1 = OpTypeInt 16 0\n%2 = OpConstant %1 65535\n",
        "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 -32768\n",
        "%1 = OpTypeInt 16 1\n%2 = OpConstant %1 32767\n",
        "%1 = OpTypeInt 32 0\n%2 = OpConstant %1 0\n",
        // 32 bit
        std::string("%1 = OpTypeInt 32 0\n%2 = OpConstant %1 0\n"),
        std::string("%1 = OpTypeInt 32 0\n%2 = OpConstant %1 ") +
            std::to_string(std::numeric_limits<uint32_t>::max()) + "\n",
        std::string("%1 = OpTypeInt 32 1\n%2 = OpConstant %1 ") +
            std::to_string(std::numeric_limits<int32_t>::max()) + "\n",
        std::string("%1 = OpTypeInt 32 1\n%2 = OpConstant %1 ") +
            std::to_string(std::numeric_limits<int32_t>::min()) + "\n",
        // 48 bit
        std::string("%1 = OpTypeInt 48 0\n%2 = OpConstant %1 0\n"),
        std::string("%1 = OpTypeInt 48 0\n%2 = OpConstant %1 ") +
            std::to_string(kMaxUnsigned48Bit) + "\n",
        std::string("%1 = OpTypeInt 48 1\n%2 = OpConstant %1 ") +
            std::to_string(kMaxSigned48Bit) + "\n",
        std::string("%1 = OpTypeInt 48 1\n%2 = OpConstant %1 ") +
            std::to_string(kMinSigned48Bit) + "\n",
        // 64 bit
        std::string("%1 = OpTypeInt 64 0\n%2 = OpConstant %1 0\n"),
        std::string("%1 = OpTypeInt 64 0\n%2 = OpConstant %1 ") +
            std::to_string(std::numeric_limits<uint64_t>::max()) + "\n",
        std::string("%1 = OpTypeInt 64 1\n%2 = OpConstant %1 ") +
            std::to_string(std::numeric_limits<int64_t>::max()) + "\n",
        std::string("%1 = OpTypeInt 64 1\n%2 = OpConstant %1 ") +
            std::to_string(std::numeric_limits<int64_t>::min()) + "\n",
        // 32-bit float
        "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0\n",
        "%1 = OpTypeFloat 32\n%2 = OpConstant %1 13.5\n",
        "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -12.5\n",
        // 64-bit float
        "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0\n",
        "%1 = OpTypeFloat 64\n%2 = OpConstant %1 1.79767e+308\n",
        "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -1.79767e+308\n",
    }));

INSTANTIATE_TEST_SUITE_P(
    OpConstantHalfRoundTrip, ConstantRoundTripTest,
    ::testing::ValuesIn(std::vector<std::string>{
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x0p+0\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x0p+0\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1p+0\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.1p+0\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.01p-1\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.8p+1\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ffcp+1\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1p+0\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.1p+0\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.01p-1\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.8p+1\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffcp+1\n",

        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1p-16\n",  // some denorms
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1p-24\n",
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1p-24\n",

        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1p+16\n",       // +inf
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1p+16\n",      // -inf
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.01p+16\n",   // -inf
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.01p+16\n",    // nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.11p+16\n",    // nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ffp+16\n",    // nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.ffcp+16\n",   // nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 0x1.004p+16\n",   // nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.01p+16\n",   // -nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.11p+16\n",   // -nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffp+16\n",   // -nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.ffcp+16\n",  // -nan
        "%1 = OpTypeFloat 16\n%2 = OpConstant %1 -0x1.004p+16\n",  // -nan
    }));

// clang-format off
// (Clang-format really wants to break up these strings across lines.
INSTANTIATE_TEST_SUITE_P(
    OpConstantRoundTripNonFinite, ConstantRoundTripTest,
    ::testing::ValuesIn(std::vector<std::string>{
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1p+128\n",         // -inf
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1p+128\n",          // inf
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.8p+128\n",       // -nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.0002p+128\n",    // -nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.0018p+128\n",    // -nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.01ep+128\n",     // -nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 -0x1.fffffep+128\n",  // -nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.8p+128\n",        // +nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.0002p+128\n",     // +nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.0018p+128\n",     // +nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.01ep+128\n",      // +nan
  "%1 = OpTypeFloat 32\n%2 = OpConstant %1 0x1.fffffep+128\n",   // +nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1p+1024\n",                // -inf
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1p+1024\n",                 // +inf
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.8p+1024\n",              // -nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.0fp+1024\n",             // -nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.0000000000001p+1024\n",  // -nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.00003p+1024\n",          // -nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 -0x1.fffffffffffffp+1024\n",  // -nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.8p+1024\n",               // +nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.0fp+1024\n",              // +nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.0000000000001p+1024\n",   // -nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.00003p+1024\n",           // -nan
  "%1 = OpTypeFloat 64\n%2 = OpConstant %1 0x1.fffffffffffffp+1024\n",   // -nan
    }));
// clang-format on

INSTANTIATE_TEST_SUITE_P(
    OpSpecConstantRoundTrip, ConstantRoundTripTest,
    ::testing::ValuesIn(std::vector<std::string>{
        // 16 bit
        "%1 = OpTypeInt 16 0\n%2 = OpSpecConstant %1 0\n",
        "%1 = OpTypeInt 16 0\n%2 = OpSpecConstant %1 65535\n",
        "%1 = OpTypeInt 16 1\n%2 = OpSpecConstant %1 -32768\n",
        "%1 = OpTypeInt 16 1\n%2 = OpSpecConstant %1 32767\n",
        "%1 = OpTypeInt 32 0\n%2 = OpSpecConstant %1 0\n",
        // 32 bit
        std::string("%1 = OpTypeInt 32 0\n%2 = OpSpecConstant %1 0\n"),
        std::string("%1 = OpTypeInt 32 0\n%2 = OpSpecConstant %1 ") +
            std::to_string(std::numeric_limits<uint32_t>::max()) + "\n",
        std::string("%1 = OpTypeInt 32 1\n%2 = OpSpecConstant %1 ") +
            std::to_string(std::numeric_limits<int32_t>::max()) + "\n",
        std::string("%1 = OpTypeInt 32 1\n%2 = OpSpecConstant %1 ") +
            std::to_string(std::numeric_limits<int32_t>::min()) + "\n",
        // 48 bit
        std::string("%1 = OpTypeInt 48 0\n%2 = OpSpecConstant %1 0\n"),
        std::string("%1 = OpTypeInt 48 0\n%2 = OpSpecConstant %1 ") +
            std::to_string(kMaxUnsigned48Bit) + "\n",
        std::string("%1 = OpTypeInt 48 1\n%2 = OpSpecConstant %1 ") +
            std::to_string(kMaxSigned48Bit) + "\n",
        std::string("%1 = OpTypeInt 48 1\n%2 = OpSpecConstant %1 ") +
            std::to_string(kMinSigned48Bit) + "\n",
        // 64 bit
        std::string("%1 = OpTypeInt 64 0\n%2 = OpSpecConstant %1 0\n"),
        std::string("%1 = OpTypeInt 64 0\n%2 = OpSpecConstant %1 ") +
            std::to_string(std::numeric_limits<uint64_t>::max()) + "\n",
        std::string("%1 = OpTypeInt 64 1\n%2 = OpSpecConstant %1 ") +
            std::to_string(std::numeric_limits<int64_t>::max()) + "\n",
        std::string("%1 = OpTypeInt 64 1\n%2 = OpSpecConstant %1 ") +
            std::to_string(std::numeric_limits<int64_t>::min()) + "\n",
        // 32-bit float
        "%1 = OpTypeFloat 32\n%2 = OpSpecConstant %1 0\n",
        "%1 = OpTypeFloat 32\n%2 = OpSpecConstant %1 13.5\n",
        "%1 = OpTypeFloat 32\n%2 = OpSpecConstant %1 -12.5\n",
        // 64-bit float
        "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 0\n",
        "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 1.79767e+308\n",
        "%1 = OpTypeFloat 64\n%2 = OpSpecConstant %1 -1.79767e+308\n",
    }));

// Test OpSpecConstantOp

using OpSpecConstantOpTestWithIds =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<EnumCase<spv::Op>>>;

// The operands to the OpSpecConstantOp opcode are all Ids.
TEST_P(OpSpecConstantOpTestWithIds, Assembly) {
  std::stringstream input;
  input << "%2 = OpSpecConstantOp %1 " << GetParam().name();
  for (auto id : GetParam().operands()) input << " %" << id;
  input << "\n";

  EXPECT_THAT(CompiledInstructions(input.str()),
              Eq(MakeInstruction(spv::Op::OpSpecConstantOp,
                                 {1, 2, uint32_t(GetParam().value())},
                                 GetParam().operands())));

  // Check the disassembler as well.
  EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str());
}

// clang-format off
#define CASE1(NAME) { spv::Op::Op##NAME, #NAME, {3} }
#define CASE2(NAME) { spv::Op::Op##NAME, #NAME, {3, 4} }
#define CASE3(NAME) { spv::Op::Op##NAME, #NAME, {3, 4, 5} }
#define CASE4(NAME) { spv::Op::Op##NAME, #NAME, {3, 4, 5, 6} }
#define CASE5(NAME) { spv::Op::Op##NAME, #NAME, {3, 4, 5, 6, 7} }
#define CASE6(NAME) { spv::Op::Op##NAME, #NAME, {3, 4, 5, 6, 7, 8} }
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryOpSpecConstantOp, OpSpecConstantOpTestWithIds,
    ::testing::ValuesIn(std::vector<EnumCase<spv::Op>>{
        // Conversion
        CASE1(SConvert),
        CASE1(FConvert),
        CASE1(ConvertFToS),
        CASE1(ConvertSToF),
        CASE1(ConvertFToU),
        CASE1(ConvertUToF),
        CASE1(UConvert),
        CASE1(ConvertPtrToU),
        CASE1(ConvertUToPtr),
        CASE1(GenericCastToPtr),
        CASE1(PtrCastToGeneric),
        CASE1(Bitcast),
        CASE1(QuantizeToF16),
        // Arithmetic
        CASE1(SNegate),
        CASE1(Not),
        CASE2(IAdd),
        CASE2(ISub),
        CASE2(IMul),
        CASE2(UDiv),
        CASE2(SDiv),
        CASE2(UMod),
        CASE2(SRem),
        CASE2(SMod),
        CASE2(ShiftRightLogical),
        CASE2(ShiftRightArithmetic),
        CASE2(ShiftLeftLogical),
        CASE2(BitwiseOr),
        CASE2(BitwiseAnd),
        CASE2(BitwiseXor),
        CASE1(FNegate),
        CASE2(FAdd),
        CASE2(FSub),
        CASE2(FMul),
        CASE2(FDiv),
        CASE2(FRem),
        CASE2(FMod),
        // Composite operations use literal numbers. So they're in another test.
        // Logical
        CASE2(LogicalOr),
        CASE2(LogicalAnd),
        CASE1(LogicalNot),
        CASE2(LogicalEqual),
        CASE2(LogicalNotEqual),
        CASE3(Select),
        // Comparison
        CASE2(IEqual),
        CASE2(INotEqual), // Allowed in 1.0 Rev 7
        CASE2(ULessThan),
        CASE2(SLessThan),
        CASE2(UGreaterThan),
        CASE2(SGreaterThan),
        CASE2(ULessThanEqual),
        CASE2(SLessThanEqual),
        CASE2(UGreaterThanEqual),
        CASE2(SGreaterThanEqual),
        // Memory
        // For AccessChain, there is a base Id, then a sequence of index Ids.
        // Having no index Ids is a corner case.
        CASE1(AccessChain),
        CASE2(AccessChain),
        CASE6(AccessChain),
        CASE1(InBoundsAccessChain),
        CASE2(InBoundsAccessChain),
        CASE6(InBoundsAccessChain),
        // PtrAccessChain also has an element Id.
        CASE2(PtrAccessChain),
        CASE3(PtrAccessChain),
        CASE6(PtrAccessChain),
        CASE2(InBoundsPtrAccessChain),
        CASE3(InBoundsPtrAccessChain),
        CASE6(InBoundsPtrAccessChain),
    }));
#undef CASE1
#undef CASE2
#undef CASE3
#undef CASE4
#undef CASE5
#undef CASE6
// clang-format on

using OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<EnumCase<spv::Op>>>;

// The operands to the OpSpecConstantOp opcode are two Ids followed by a
// sequence of literal numbers.
TEST_P(OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers, Assembly) {
  std::stringstream input;
  input << "%2 = OpSpecConstantOp %1 " << GetParam().name() << " %3 %4";
  for (auto number : GetParam().operands()) input << " " << number;
  input << "\n";

  EXPECT_THAT(CompiledInstructions(input.str()),
              Eq(MakeInstruction(spv::Op::OpSpecConstantOp,
                                 {1, 2, uint32_t(GetParam().value()), 3, 4},
                                 GetParam().operands())));

  // Check the disassembler as well.
  EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str());
}

#define CASE(NAME) spv::Op::Op##NAME, #NAME
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryOpSpecConstantOp,
    OpSpecConstantOpTestWithTwoIdsThenLiteralNumbers,
    ::testing::ValuesIn(std::vector<EnumCase<spv::Op>>{
        // For VectorShuffle, there are two vector operands, and at least
        // two selector Ids.  OpenCL can have up to 16-element vectors.
        {CASE(VectorShuffle), {0, 0}},
        {CASE(VectorShuffle), {4, 3, 2, 1}},
        {CASE(VectorShuffle), {0, 2, 4, 6, 1, 3, 5, 7}},
        {CASE(VectorShuffle),
         {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}},
        // For CompositeInsert, there is an object to insert, the target
        // composite, and then literal indices.
        {CASE(CompositeInsert), {0}},
        {CASE(CompositeInsert), {4, 3, 99, 1}},
    }));

using OpSpecConstantOpTestWithOneIdThenLiteralNumbers =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<EnumCase<spv::Op>>>;

// The operands to the OpSpecConstantOp opcode are one Id followed by a
// sequence of literal numbers.
TEST_P(OpSpecConstantOpTestWithOneIdThenLiteralNumbers, Assembly) {
  std::stringstream input;
  input << "%2 = OpSpecConstantOp %1 " << GetParam().name() << " %3";
  for (auto number : GetParam().operands()) input << " " << number;
  input << "\n";

  EXPECT_THAT(CompiledInstructions(input.str()),
              Eq(MakeInstruction(spv::Op::OpSpecConstantOp,
                                 {1, 2, uint32_t(GetParam().value()), 3},
                                 GetParam().operands())));

  // Check the disassembler as well.
  EXPECT_THAT(EncodeAndDecodeSuccessfully(input.str()), input.str());
}

#define CASE(NAME) spv::Op::Op##NAME, #NAME
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryOpSpecConstantOp,
    OpSpecConstantOpTestWithOneIdThenLiteralNumbers,
    ::testing::ValuesIn(std::vector<EnumCase<spv::Op>>{
        // For CompositeExtract, the universal limit permits up to 255 literal
        // indices.  Let's only test a few.
        {CASE(CompositeExtract), {0}},
        {CASE(CompositeExtract), {0, 99, 42, 16, 17, 12, 19}},
    }));

using TextToBinaryOpSpecConstantOpNegative =
    spvtest::TextToBinaryTestBase<::testing::Test>;

TEST_F(TextToBinaryOpSpecConstantOpNegative, BadEnum) {
  std::string input = "%1 = OpSpecConstantOp %1 Add %3 %4";
  EXPECT_THAT(CompileFailure(input),
              testing::HasSubstr("Invalid OpSpecConstantOp opcode 'Add'"));
}

TEST_F(TextToBinaryOpSpecConstantOpNegative, NearMiss) {
  std::string input = "%1 = OpSpecConstantOp %1 OpIAdd %3 %4";
  EXPECT_THAT(
      CompileFailure(input),
      testing::HasSubstr(
          "Invalid OpSpecConstantOp opcode 'OpIAdd'. Did you mean 'IAdd'?"));
}

// TODO(dneto): OpConstantTrue
// TODO(dneto): OpConstantFalse
// TODO(dneto): OpConstantComposite
// TODO(dneto): OpConstantSampler: other variations Param is 0 or 1
// TODO(dneto): OpConstantNull
// TODO(dneto): OpSpecConstantTrue
// TODO(dneto): OpSpecConstantFalse
// TODO(dneto): OpSpecConstantComposite
// TODO(dneto): Negative tests for OpSpecConstantOp

}  // namespace
}  // namespace spvtools
