// 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 "Annotation" section of the
// SPIR-V spec.

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

#include "gmock/gmock.h"
#include "source/util/string_utils.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"

namespace spvtools {
namespace {

using spvtest::EnumCase;
using spvtest::MakeInstruction;
using utils::MakeVector;
using spvtest::TextToBinaryTest;
using ::testing::Combine;
using ::testing::Eq;
using ::testing::Values;
using ::testing::ValuesIn;

// Test OpDecorate

using OpDecorateSimpleTest =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<
        std::tuple<spv_target_env, EnumCase<spv::Decoration>>>>;

TEST_P(OpDecorateSimpleTest, AnySimpleDecoration) {
  // This string should assemble, but should not validate.
  std::stringstream input;
  input << "OpDecorate %1 " << std::get<1>(GetParam()).name();
  for (auto operand : std::get<1>(GetParam()).operands())
    input << " " << operand;
  input << std::endl;
  EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())),
              Eq(MakeInstruction(spv::Op::OpDecorate,
                                 {1, uint32_t(std::get<1>(GetParam()).value())},
                                 std::get<1>(GetParam()).operands())));
  // Also check disassembly.
  EXPECT_THAT(
      EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
                                  std::get<0>(GetParam())),
      Eq(input.str()));
}

// Like above, but parameters to the decoration are IDs.
using OpDecorateSimpleIdTest =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<
        std::tuple<spv_target_env, EnumCase<spv::Decoration>>>>;

TEST_P(OpDecorateSimpleIdTest, AnySimpleDecoration) {
  // This string should assemble, but should not validate.
  std::stringstream input;
  input << "OpDecorateId %1 " << std::get<1>(GetParam()).name();
  for (auto operand : std::get<1>(GetParam()).operands())
    input << " %" << operand;
  input << std::endl;
  EXPECT_THAT(CompiledInstructions(input.str(), std::get<0>(GetParam())),
              Eq(MakeInstruction(spv::Op::OpDecorateId,
                                 {1, uint32_t(std::get<1>(GetParam()).value())},
                                 std::get<1>(GetParam()).operands())));
  // Also check disassembly.
  EXPECT_THAT(
      EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
                                  std::get<0>(GetParam())),
      Eq(input.str()));
}

#define CASE(NAME) spv::Decoration::NAME, #NAME
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryDecorateSimple, OpDecorateSimpleTest,
    Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
            ValuesIn(std::vector<EnumCase<spv::Decoration>>{
                // The operand literal values are arbitrarily chosen,
                // but there are the right number of them.
                {CASE(RelaxedPrecision), {}},
                {CASE(SpecId), {100}},
                {CASE(Block), {}},
                {CASE(BufferBlock), {}},
                {CASE(RowMajor), {}},
                {CASE(ColMajor), {}},
                {CASE(ArrayStride), {4}},
                {CASE(MatrixStride), {16}},
                {CASE(GLSLShared), {}},
                {CASE(GLSLPacked), {}},
                {CASE(CPacked), {}},
                // Placeholder line for enum value 12
                {CASE(NoPerspective), {}},
                {CASE(Flat), {}},
                {CASE(Patch), {}},
                {CASE(Centroid), {}},
                {CASE(Sample), {}},
                {CASE(Invariant), {}},
                {CASE(Restrict), {}},
                {CASE(Aliased), {}},
                {CASE(Volatile), {}},
                {CASE(Constant), {}},
                {CASE(Coherent), {}},
                {CASE(NonWritable), {}},
                {CASE(NonReadable), {}},
                {CASE(Uniform), {}},
                {CASE(SaturatedConversion), {}},
                {CASE(Stream), {2}},
                {CASE(Location), {6}},
                {CASE(Component), {3}},
                {CASE(Index), {14}},
                {CASE(Binding), {19}},
                {CASE(DescriptorSet), {7}},
                {CASE(Offset), {12}},
                {CASE(XfbBuffer), {1}},
                {CASE(XfbStride), {8}},
                {CASE(NoContraction), {}},
                {CASE(InputAttachmentIndex), {102}},
                {CASE(Alignment), {16}},
            })));

INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateSimpleV11, OpDecorateSimpleTest,
                         Combine(Values(SPV_ENV_UNIVERSAL_1_1),
                                 Values(EnumCase<spv::Decoration>{
                                     CASE(MaxByteOffset), {128}})));

INSTANTIATE_TEST_SUITE_P(
    TextToBinaryDecorateSimpleV14, OpDecorateSimpleTest,
    Combine(Values(SPV_ENV_UNIVERSAL_1_4),
            ValuesIn(std::vector<EnumCase<spv::Decoration>>{
                {CASE(Uniform), {}},
            })));

INSTANTIATE_TEST_SUITE_P(
    TextToBinaryDecorateSimpleIdV14, OpDecorateSimpleIdTest,
    Combine(Values(SPV_ENV_UNIVERSAL_1_4),
            ValuesIn(std::vector<EnumCase<spv::Decoration>>{
                // In 1.4, UniformId decoration takes a
                // scope Id.
                {CASE(UniformId), {1}},
            })));
#undef CASE

TEST_F(OpDecorateSimpleTest, WrongDecoration) {
  EXPECT_THAT(CompileFailure("OpDecorate %1 xxyyzz"),
              Eq("Invalid decoration 'xxyyzz'."));
}

TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingNone) {
  EXPECT_THAT(CompileFailure("OpDecorate %1 RelaxedPrecision 99"),
              Eq("Expected <opcode> or <result-id> at the beginning of an "
                 "instruction, found '99'."));
}

TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingOne) {
  EXPECT_THAT(CompileFailure("OpDecorate %1 SpecId 99 100"),
              Eq("Expected <opcode> or <result-id> at the beginning of an "
                 "instruction, found '100'."));
}

TEST_F(OpDecorateSimpleTest, ExtraOperandsOnDecorationExpectingTwo) {
  EXPECT_THAT(
      CompileFailure("OpDecorate %1 LinkageAttributes \"abc\" Import 42"),
      Eq("Expected <opcode> or <result-id> at the beginning of an "
         "instruction, found '42'."));
}

// A single test case for an enum decoration.
struct DecorateEnumCase {
  // Place the enum value first, so it's easier to read the binary dumps when
  // the test fails.
  uint32_t value;  // The value within the enum, e.g. Position
  std::string name;
  uint32_t enum_value;  // Which enum, e.g. BuiltIn
  std::string enum_name;
};

using OpDecorateEnumTest =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<DecorateEnumCase>>;

TEST_P(OpDecorateEnumTest, AnyEnumDecoration) {
  // This string should assemble, but should not validate.
  const std::string input =
      "OpDecorate %1 " + GetParam().enum_name + " " + GetParam().name;
  EXPECT_THAT(CompiledInstructions(input),
              Eq(MakeInstruction(spv::Op::OpDecorate, {1, GetParam().enum_value,
                                                       GetParam().value})));
}

// Test OpDecorate BuiltIn.
// clang-format off
#define CASE(NAME) \
  { uint32_t(spv::BuiltIn::NAME), #NAME, uint32_t(spv::Decoration::BuiltIn), "BuiltIn" }
INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateBuiltIn, OpDecorateEnumTest,
                        ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                            CASE(Position),
                            CASE(PointSize),
                            CASE(ClipDistance),
                            CASE(CullDistance),
                            CASE(VertexId),
                            CASE(InstanceId),
                            CASE(PrimitiveId),
                            CASE(InvocationId),
                            CASE(Layer),
                            CASE(ViewportIndex),
                            CASE(TessLevelOuter),
                            CASE(TessLevelInner),
                            CASE(TessCoord),
                            CASE(PatchVertices),
                            CASE(FragCoord),
                            CASE(PointCoord),
                            CASE(FrontFacing),
                            CASE(SampleId),
                            CASE(SamplePosition),
                            CASE(SampleMask),
                            // Value 21 intentionally missing.
                            CASE(FragDepth),
                            CASE(HelperInvocation),
                            CASE(NumWorkgroups),
                            CASE(WorkgroupSize),
                            CASE(WorkgroupId),
                            CASE(LocalInvocationId),
                            CASE(GlobalInvocationId),
                            CASE(LocalInvocationIndex),
                            CASE(WorkDim),
                            CASE(GlobalSize),
                            CASE(EnqueuedWorkgroupSize),
                            CASE(GlobalOffset),
                            CASE(GlobalLinearId),
                            // Value 35 intentionally missing.
                            CASE(SubgroupSize),
                            CASE(SubgroupMaxSize),
                            CASE(NumSubgroups),
                            CASE(NumEnqueuedSubgroups),
                            CASE(SubgroupId),
                            CASE(SubgroupLocalInvocationId),
                            CASE(VertexIndex),
                            CASE(InstanceIndex),
                        }));
#undef CASE
// clang-format on

TEST_F(OpDecorateEnumTest, WrongBuiltIn) {
  EXPECT_THAT(CompileFailure("OpDecorate %1 BuiltIn xxyyzz"),
              Eq("Invalid built-in 'xxyyzz'."));
}

// Test OpDecorate FuncParamAttr
// clang-format off
#define CASE(NAME) \
  { uint32_t(spv::FunctionParameterAttribute::NAME), #NAME, uint32_t(spv::Decoration::FuncParamAttr), "FuncParamAttr" }
INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFuncParamAttr, OpDecorateEnumTest,
                        ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                            CASE(Zext),
                            CASE(Sext),
                            CASE(ByVal),
                            CASE(Sret),
                            CASE(NoAlias),
                            CASE(NoCapture),
                            CASE(NoWrite),
                            CASE(NoReadWrite),
                      }));
#undef CASE
// clang-format on

TEST_F(OpDecorateEnumTest, WrongFuncParamAttr) {
  EXPECT_THAT(CompileFailure("OpDecorate %1 FuncParamAttr xxyyzz"),
              Eq("Invalid function parameter attribute 'xxyyzz'."));
}

// Test OpDecorate FPRoundingMode
// clang-format off
#define CASE(NAME) \
  { uint32_t(spv::FPRoundingMode::NAME), #NAME, uint32_t(spv::Decoration::FPRoundingMode), "FPRoundingMode" }
INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFPRoundingMode, OpDecorateEnumTest,
                        ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                            CASE(RTE),
                            CASE(RTZ),
                            CASE(RTP),
                            CASE(RTN),
                      }));
#undef CASE
// clang-format on

TEST_F(OpDecorateEnumTest, WrongFPRoundingMode) {
  EXPECT_THAT(CompileFailure("OpDecorate %1 FPRoundingMode xxyyzz"),
              Eq("Invalid floating-point rounding mode 'xxyyzz'."));
}

// Test OpDecorate FPFastMathMode.
// These can by named enums for the single-bit masks.  However, we don't support
// symbolic combinations of the masks.  Rather, they can use !<immediate>
// syntax, e.g. !0x3

// clang-format off
#define CASE(ENUM,NAME) \
  { uint32_t(spv::FPFastMathModeMask::ENUM), #NAME, uint32_t(spv::Decoration::FPFastMathMode), "FPFastMathMode" }
INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateFPFastMathMode, OpDecorateEnumTest,
                        ::testing::ValuesIn(std::vector<DecorateEnumCase>{
                            CASE(MaskNone, None),
                            CASE(NotNaN, NotNaN),
                            CASE(NotInf, NotInf),
                            CASE(NSZ, NSZ),
                            CASE(AllowRecip, AllowRecip),
                            CASE(Fast, Fast),
                      }));
#undef CASE
// clang-format on

TEST_F(OpDecorateEnumTest, CombinedFPFastMathMask) {
  // Sample a single combination.  This ensures we've integrated
  // the instruction parsing logic with spvTextParseMask.
  const std::string input = "OpDecorate %1 FPFastMathMode NotNaN|NotInf|NSZ";
  const uint32_t expected_enum = uint32_t(spv::Decoration::FPFastMathMode);
  const uint32_t expected_mask = uint32_t(spv::FPFastMathModeMask::NotNaN) |
                                 uint32_t(spv::FPFastMathModeMask::NotInf) |
                                 uint32_t(spv::FPFastMathModeMask::NSZ);
  EXPECT_THAT(CompiledInstructions(input),
              Eq(MakeInstruction(spv::Op::OpDecorate,
                                 {1, expected_enum, expected_mask})));
}

TEST_F(OpDecorateEnumTest, WrongFPFastMathMode) {
  EXPECT_THAT(
      CompileFailure("OpDecorate %1 FPFastMathMode NotNaN|xxyyzz"),
      Eq("Invalid floating-point fast math mode operand 'NotNaN|xxyyzz'."));
}

// Test OpDecorate Linkage

// A single test case for a linkage
struct DecorateLinkageCase {
  uint32_t linkage_type_value;
  std::string linkage_type_name;
  std::string external_name;
};

using OpDecorateLinkageTest = spvtest::TextToBinaryTestBase<
    ::testing::TestWithParam<DecorateLinkageCase>>;

TEST_P(OpDecorateLinkageTest, AnyLinkageDecoration) {
  // This string should assemble, but should not validate.
  const std::string input = "OpDecorate %1 LinkageAttributes \"" +
                            GetParam().external_name + "\" " +
                            GetParam().linkage_type_name;
  std::vector<uint32_t> expected_operands{
      1, uint32_t(spv::Decoration::LinkageAttributes)};
  std::vector<uint32_t> encoded_external_name =
      MakeVector(GetParam().external_name);
  expected_operands.insert(expected_operands.end(),
                           encoded_external_name.begin(),
                           encoded_external_name.end());
  expected_operands.push_back(GetParam().linkage_type_value);
  EXPECT_THAT(CompiledInstructions(input),
              Eq(MakeInstruction(spv::Op::OpDecorate, expected_operands)));
}

// clang-format off
#define CASE(ENUM) uint32_t(spv::LinkageType::ENUM), #ENUM
INSTANTIATE_TEST_SUITE_P(TextToBinaryDecorateLinkage, OpDecorateLinkageTest,
                        ::testing::ValuesIn(std::vector<DecorateLinkageCase>{
                            { CASE(Import), "a" },
                            { CASE(Export), "foo" },
                            { CASE(Import), "some kind of long name with spaces etc." },
                            // TODO(dneto): utf-8, escaping, quoting cases.
                      }));
#undef CASE
// clang-format on

TEST_F(OpDecorateLinkageTest, WrongType) {
  EXPECT_THAT(CompileFailure("OpDecorate %1 LinkageAttributes \"foo\" xxyyzz"),
              Eq("Invalid linkage type 'xxyyzz'."));
}

// Test OpGroupMemberDecorate

TEST_F(TextToBinaryTest, GroupMemberDecorateGoodOneTarget) {
  EXPECT_THAT(CompiledInstructions("OpGroupMemberDecorate %group %id0 42"),
              Eq(MakeInstruction(spv::Op::OpGroupMemberDecorate, {1, 2, 42})));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateGoodTwoTargets) {
  EXPECT_THAT(
      CompiledInstructions("OpGroupMemberDecorate %group %id0 96 %id1 42"),
      Eq(MakeInstruction(spv::Op::OpGroupMemberDecorate, {1, 2, 96, 3, 42})));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateMissingGroupId) {
  EXPECT_THAT(CompileFailure("OpGroupMemberDecorate"),
              Eq("Expected operand for OpGroupMemberDecorate instruction, but "
                 "found the end of the stream."));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidGroupId) {
  EXPECT_THAT(CompileFailure("OpGroupMemberDecorate 16"),
              Eq("Expected id to start with %."));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidTargetId) {
  EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group 12"),
              Eq("Expected id to start with %."));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateMissingTargetMemberNumber) {
  EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0"),
              Eq("Expected operand for OpGroupMemberDecorate instruction, but "
                 "found the end of the stream."));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidTargetMemberNumber) {
  EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 %id1"),
              Eq("Invalid unsigned integer literal: %id1"));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidSecondTargetId) {
  EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id1 42 12"),
              Eq("Expected id to start with %."));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateMissingSecondTargetMemberNumber) {
  EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 42 %id1"),
              Eq("Expected operand for OpGroupMemberDecorate instruction, but "
                 "found the end of the stream."));
}

TEST_F(TextToBinaryTest, GroupMemberDecorateInvalidSecondTargetMemberNumber) {
  EXPECT_THAT(CompileFailure("OpGroupMemberDecorate %group %id0 42 %id1 %id2"),
              Eq("Invalid unsigned integer literal: %id2"));
}

// Test OpMemberDecorate

using OpMemberDecorateSimpleTest =
    spvtest::TextToBinaryTestBase<::testing::TestWithParam<
        std::tuple<spv_target_env, EnumCase<spv::Decoration>>>>;

TEST_P(OpMemberDecorateSimpleTest, AnySimpleDecoration) {
  // This string should assemble, but should not validate.
  std::stringstream input;
  input << "OpMemberDecorate %1 42 " << std::get<1>(GetParam()).name();
  for (auto operand : std::get<1>(GetParam()).operands())
    input << " " << operand;
  input << std::endl;
  EXPECT_THAT(
      CompiledInstructions(input.str(), std::get<0>(GetParam())),
      Eq(MakeInstruction(spv::Op::OpMemberDecorate,
                         {1, 42, uint32_t(std::get<1>(GetParam()).value())},
                         std::get<1>(GetParam()).operands())));
  // Also check disassembly.
  EXPECT_THAT(
      EncodeAndDecodeSuccessfully(input.str(), SPV_BINARY_TO_TEXT_OPTION_NONE,
                                  std::get<0>(GetParam())),
      Eq(input.str()));
}

#define CASE(NAME) spv::Decoration::NAME, #NAME
INSTANTIATE_TEST_SUITE_P(
    TextToBinaryDecorateSimple, OpMemberDecorateSimpleTest,
    Combine(Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_1),
            ValuesIn(std::vector<EnumCase<spv::Decoration>>{
                // The operand literal values are arbitrarily chosen,
                // but there are the right number of them.
                {CASE(RelaxedPrecision), {}},
                {CASE(SpecId), {100}},
                {CASE(Block), {}},
                {CASE(BufferBlock), {}},
                {CASE(RowMajor), {}},
                {CASE(ColMajor), {}},
                {CASE(ArrayStride), {4}},
                {CASE(MatrixStride), {16}},
                {CASE(GLSLShared), {}},
                {CASE(GLSLPacked), {}},
                {CASE(CPacked), {}},
                // Placeholder line for enum value 12
                {CASE(NoPerspective), {}},
                {CASE(Flat), {}},
                {CASE(Patch), {}},
                {CASE(Centroid), {}},
                {CASE(Sample), {}},
                {CASE(Invariant), {}},
                {CASE(Restrict), {}},
                {CASE(Aliased), {}},
                {CASE(Volatile), {}},
                {CASE(Constant), {}},
                {CASE(Coherent), {}},
                {CASE(NonWritable), {}},
                {CASE(NonReadable), {}},
                {CASE(Uniform), {}},
                {CASE(SaturatedConversion), {}},
                {CASE(Stream), {2}},
                {CASE(Location), {6}},
                {CASE(Component), {3}},
                {CASE(Index), {14}},
                {CASE(Binding), {19}},
                {CASE(DescriptorSet), {7}},
                {CASE(Offset), {12}},
                {CASE(XfbBuffer), {1}},
                {CASE(XfbStride), {8}},
                {CASE(NoContraction), {}},
                {CASE(InputAttachmentIndex), {102}},
                {CASE(Alignment), {16}},
            })));

INSTANTIATE_TEST_SUITE_P(
    TextToBinaryDecorateSimpleV11, OpMemberDecorateSimpleTest,
    Combine(Values(SPV_ENV_UNIVERSAL_1_1),
            Values(EnumCase<spv::Decoration>{CASE(MaxByteOffset), {128}})));
#undef CASE

TEST_F(OpMemberDecorateSimpleTest, WrongDecoration) {
  EXPECT_THAT(CompileFailure("OpMemberDecorate %1 9 xxyyzz"),
              Eq("Invalid decoration 'xxyyzz'."));
}

TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingNone) {
  EXPECT_THAT(CompileFailure("OpMemberDecorate %1 12 RelaxedPrecision 99"),
              Eq("Expected <opcode> or <result-id> at the beginning of an "
                 "instruction, found '99'."));
}

TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingOne) {
  EXPECT_THAT(CompileFailure("OpMemberDecorate %1 0 SpecId 99 100"),
              Eq("Expected <opcode> or <result-id> at the beginning of an "
                 "instruction, found '100'."));
}

TEST_F(OpMemberDecorateSimpleTest, ExtraOperandsOnDecorationExpectingTwo) {
  EXPECT_THAT(CompileFailure(
                  "OpMemberDecorate %1 1 LinkageAttributes \"abc\" Import 42"),
              Eq("Expected <opcode> or <result-id> at the beginning of an "
                 "instruction, found '42'."));
}

// TODO(dneto): OpMemberDecorate cases for decorations with parameters which
// are: not just lists of literal numbers.

// TODO(dneto): OpDecorationGroup
// TODO(dneto): OpGroupDecorate

}  // namespace
}  // namespace spvtools
