// Copyright (c) 2017 Google 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.

// Validation tests for illegal literals

#include <string>
#include <utility>

#include "gmock/gmock.h"
#include "test/val/val_fixtures.h"

namespace spvtools {
namespace val {
namespace {

using ::testing::HasSubstr;

using ValidateLiterals = spvtest::ValidateBase<std::string>;
using ValidateLiteralsShader = spvtest::ValidateBase<std::string>;
using ValidateLiteralsKernel = spvtest::ValidateBase<std::string>;

std::string GenerateShaderCode() {
  std::string str = R"(
          OpCapability Shader
          OpCapability Linkage
          OpCapability Int16
          OpCapability Int64
          OpCapability Float16
          OpCapability Float64
          OpMemoryModel Logical GLSL450
%int16  = OpTypeInt 16 1
%uint16 = OpTypeInt 16 0
%int32  = OpTypeInt 32 1
%uint32 = OpTypeInt 32 0
%int64  = OpTypeInt 64 1
%uint64 = OpTypeInt 64 0
%half   = OpTypeFloat 16
%float  = OpTypeFloat 32
%double = OpTypeFloat 64
%10     = OpTypeVoid
    )";
  return str;
}

std::string GenerateKernelCode() {
  std::string str = R"(
          OpCapability Kernel
          OpCapability Addresses
          OpCapability Linkage
          OpCapability Int8
          OpMemoryModel Physical64 OpenCL
%uint8  = OpTypeInt 8 0
    )";
  return str;
}

TEST_F(ValidateLiterals, LiteralsShaderGood) {
  std::string str = GenerateShaderCode() + R"(
%11 = OpConstant %int16   !0x00007FFF
%12 = OpConstant %int16   !0xFFFF8000
%13 = OpConstant %int16   !0xFFFFABCD
%14 = OpConstant %uint16  !0x0000ABCD
%15 = OpConstant %int16  -32768
%16 = OpConstant %uint16  65535
%17 = OpConstant %int32  -2147483648
%18 = OpConstant %uint32  4294967295
%19 = OpConstant %int64  -9223372036854775808
%20 = OpConstant %uint64  18446744073709551615
%21 = OpConstant %half    !0x0000FFFF
%22 = OpConstant %float   !0xFFFFFFFF
%23 = OpConstant %double  !0xFFFFFFFF !0xFFFFFFFF
  )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateLiterals, InvalidInt) {
  std::string str = GenerateShaderCode() + R"(
%11 = OpTypeInt 32 90
  )";
  CompileSuccessfully(str);
  EXPECT_EQ(SPV_ERROR_INVALID_VALUE, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpTypeInt has invalid signedness:"));
}

TEST_P(ValidateLiteralsShader, LiteralsShaderBad) {
  std::string str = GenerateShaderCode() + GetParam();
  std::string inst_id = "11";
  CompileSuccessfully(str);
  EXPECT_EQ(SPV_ERROR_INVALID_VALUE, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The high-order bits of a literal number in instruction <id> " +
                inst_id +
                " must be 0 for a floating-point type, "
                "or 0 for an integer type with Signedness of 0, "
                "or sign extended when Signedness is 1"));
}

INSTANTIATE_TEST_SUITE_P(
    LiteralsShaderCases, ValidateLiteralsShader,
    ::testing::Values("%11 = OpConstant %int16  !0xFFFF0000",  // Sign bit is 0
                      "%11 = OpConstant %int16  !0x00008000",  // Sign bit is 1
                      "%11 = OpConstant %int16  !0xABCD8000",  // Sign bit is 1
                      "%11 = OpConstant %int16  !0xABCD0000",
                      "%11 = OpConstant %uint16 !0xABCD0000",
                      "%11 = OpConstant %half   !0xABCD0000",
                      "%11 = OpConstant %half   !0x00010000"));

TEST_F(ValidateLiterals, LiteralsKernelGood) {
  std::string str = GenerateKernelCode() + R"(
%4  = OpConstant %uint8  !0x000000AB
%6  = OpConstant %uint8  255
  )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_P(ValidateLiteralsKernel, LiteralsKernelBad) {
  std::string str = GenerateKernelCode() + GetParam();
  std::string inst_id = "2";
  CompileSuccessfully(str);
  EXPECT_EQ(SPV_ERROR_INVALID_VALUE, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The high-order bits of a literal number in instruction <id> " +
                inst_id +
                " must be 0 for a floating-point type, "
                "or 0 for an integer type with Signedness of 0, "
                "or sign extended when Signedness is 1"));
}

INSTANTIATE_TEST_SUITE_P(
    LiteralsKernelCases, ValidateLiteralsKernel,
    ::testing::Values("%2 = OpConstant %uint8  !0xABCDEF00",
                      "%2 = OpConstant %uint8  !0xABCDEFFF"));

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