// 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.

// Validation tests for SSA

#include <sstream>
#include <string>
#include <utility>

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

namespace spvtools {
namespace val {
namespace {

using ::testing::HasSubstr;
using ::testing::MatchesRegex;

using ValidateSSA = spvtest::ValidateBase<std::pair<std::string, bool>>;

TEST_F(ValidateSSA, Default) {
  char str[] = R"(
     OpCapability Shader
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
     OpEntryPoint GLCompute %3 ""
     OpExecutionMode %3 LocalSize 1 1 1
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%3 = OpFunction %1 None %2
%4 = OpLabel
     OpReturn
     OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, IdUndefinedBad) {
  char str[] = R"(
          OpCapability Shader
          OpCapability Linkage
          OpMemoryModel Logical GLSL450
          OpName %missing "missing"
%voidt  = OpTypeVoid
%vfunct = OpTypeFunction %voidt
%func   = OpFunction %vfunct None %missing
%flabel = OpLabel
          OpReturn
          OpFunctionEnd
    )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, IdRedefinedBad) {
  char str[] = R"(
     OpCapability Shader
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
     OpName %2 "redefined"
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%2 = OpFunction %1 None %2
%4 = OpLabel
     OpReturn
     OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
}

TEST_F(ValidateSSA, DominateUsageBad) {
  char str[] = R"(
     OpCapability Shader
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
     OpName %1 "not_dominant"
%2 = OpTypeFunction %1              ; uses %1 before it's definition
%1 = OpTypeVoid
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("not_dominant"));
}

TEST_F(ValidateSSA, DominateUsageWithinBlockBad) {
  char str[] = R"(
     OpCapability Shader
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
     OpName %bad "bad"
%voidt = OpTypeVoid
%funct = OpTypeFunction %voidt
%uintt = OpTypeInt 32 0
%one   = OpConstant %uintt 1
%func  = OpFunction %voidt None %funct
%entry = OpLabel
%sum   = OpIAdd %uintt %one %bad
%bad   = OpCopyObject %uintt %sum
         OpReturn
         OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              MatchesRegex("ID .\\[%bad\\] has not been defined\n"
                           "  %8 = OpIAdd %uint %uint_1 %bad\n"));
}

TEST_F(ValidateSSA, DominateUsageSameInstructionBad) {
  char str[] = R"(
     OpCapability Shader
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
     OpName %sum "sum"
%voidt = OpTypeVoid
%funct = OpTypeFunction %voidt
%uintt = OpTypeInt 32 0
%one   = OpConstant %uintt 1
%func  = OpFunction %voidt None %funct
%entry = OpLabel
%sum   = OpIAdd %uintt %one %sum
         OpReturn
         OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              MatchesRegex("ID .\\[%sum\\] has not been defined\n"
                           "  %sum = OpIAdd %uint %uint_1 %sum\n"));
}

TEST_F(ValidateSSA, ForwardNameGood) {
  char str[] = R"(
     OpCapability Shader
     OpCapability Linkage
     OpMemoryModel Logical GLSL450
     OpName %3 "main"
%1 = OpTypeVoid
%2 = OpTypeFunction %1
%3 = OpFunction %1 None %2
%4 = OpLabel
     OpReturn
     OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardNameMissingTargetBad) {
  char str[] = R"(
      OpCapability Shader
      OpCapability Linkage
      OpMemoryModel Logical GLSL450
      OpName %5 "main"              ; Target never defined
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("main"));
}

TEST_F(ValidateSSA, ForwardMemberNameGood) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpMemberName %struct 0 "value"
           OpMemberName %struct 1 "size"
%intt   =  OpTypeInt 32 1
%uintt  =  OpTypeInt 32 0
%struct =  OpTypeStruct %intt %uintt
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardMemberNameMissingTargetBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpMemberName %struct 0 "value"
           OpMemberName %bad 1 "size"     ; Target is not defined
%intt   =  OpTypeInt 32 1
%uintt  =  OpTypeInt 32 0
%struct =  OpTypeStruct %intt %uintt
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("The following forward referenced IDs have not been "
                        "defined:\n2[%2]"));
}

TEST_F(ValidateSSA, ForwardDecorateGood) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpDecorate %var Restrict
%intt   =  OpTypeInt 32 1
%ptrt   =  OpTypePointer UniformConstant %intt
%var    =  OpVariable %ptrt UniformConstant
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardDecorateInvalidIDBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpName %missing "missing"
           OpDecorate %missing Restrict        ;Missing ID
%voidt  =  OpTypeVoid
%intt   =  OpTypeInt 32 1
%ptrt   =  OpTypePointer UniformConstant %intt
%var    =  OpVariable %ptrt UniformConstant
%2      =  OpTypeFunction %voidt
%3      =  OpFunction %voidt None %2
%4      =  OpLabel
           OpReturn
           OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, ForwardMemberDecorateGood) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpMemberDecorate %struct 1 RowMajor
%intt   =  OpTypeInt 32 1
%f32    =  OpTypeFloat 32
%vec3   =  OpTypeVector %f32 3
%mat33  =  OpTypeMatrix %vec3 3
%struct =  OpTypeStruct %intt %mat33
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardMemberDecorateInvalidIdBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpName %missing "missing"
           OpMemberDecorate %missing 1 RowMajor ; Target not defined
%intt   =  OpTypeInt 32 1
%f32    =  OpTypeFloat 32
%vec3   =  OpTypeVector %f32 3
%mat33  =  OpTypeMatrix %vec3 3
%struct =  OpTypeStruct %intt %mat33
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, ForwardGroupDecorateGood) {
  char str[] = R"(
          OpCapability Shader
          OpCapability Linkage
          OpMemoryModel Logical GLSL450
          OpDecorate %dgrp RowMajor
%dgrp   = OpDecorationGroup
          OpGroupDecorate %dgrp %mat33 %mat44
%f32    =  OpTypeFloat 32
%vec3   = OpTypeVector %f32 3
%vec4   = OpTypeVector %f32 4
%mat33  = OpTypeMatrix %vec3 3
%mat44  = OpTypeMatrix %vec4 4
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardGroupDecorateMissingGroupBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpName %missing "missing"
           OpDecorate %dgrp RowMajor
%dgrp   =  OpDecorationGroup
           OpGroupDecorate %missing %mat33 %mat44 ; Target not defined
%intt   =  OpTypeInt 32 1
%vec3   =  OpTypeVector %intt 3
%vec4   =  OpTypeVector %intt 4
%mat33  =  OpTypeMatrix %vec3 3
%mat44  =  OpTypeMatrix %vec4 4
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, ForwardGroupDecorateMissingTargetBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpName %missing "missing"
           OpDecorate %dgrp RowMajor
%dgrp   =  OpDecorationGroup
           OpGroupDecorate %dgrp %missing %mat44 ; Target not defined
%f32    =  OpTypeFloat 32
%vec3   =  OpTypeVector %f32 3
%vec4   =  OpTypeVector %f32 4
%mat33  =  OpTypeMatrix %vec3 3
%mat44  =  OpTypeMatrix %vec4 4
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, ForwardGroupDecorateDecorationGroupDominateBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpName %dgrp "group"
           OpDecorate %dgrp RowMajor
           OpGroupDecorate %dgrp %mat33 %mat44 ; Decoration group does not dominate usage
%dgrp   =  OpDecorationGroup
%intt   =  OpTypeInt 32 1
%vec3   =  OpTypeVector %intt 3
%vec4   =  OpTypeVector %intt 4
%mat33  =  OpTypeMatrix %vec3 3
%mat44  =  OpTypeMatrix %vec4 4
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("group"));
}

TEST_F(ValidateSSA, ForwardDecorateInvalidIdBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpName %missing "missing"
           OpDecorate %missing Restrict        ; Missing target
%voidt  =  OpTypeVoid
%intt   =  OpTypeInt 32 1
%ptrt   =  OpTypePointer UniformConstant %intt
%var    =  OpVariable %ptrt UniformConstant
%2      =  OpTypeFunction %voidt
%3      =  OpFunction %voidt None %2
%4      =  OpLabel
           OpReturn
           OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, FunctionCallGood) {
  char str[] = R"(
         OpCapability Shader
         OpCapability Linkage
         OpMemoryModel Logical GLSL450
%1    =  OpTypeVoid
%2    =  OpTypeInt 32 1
%3    =  OpTypeInt 32 0
%4    =  OpTypeFunction %1
%8    =  OpTypeFunction %1 %2 %3
%four =  OpConstant %2 4
%five =  OpConstant %3 5
%9    =  OpFunction %1 None %8
%10   =  OpFunctionParameter %2
%11   =  OpFunctionParameter %3
%12   =  OpLabel
         OpReturn
         OpFunctionEnd
%5    =  OpFunction %1 None %4
%6    =  OpLabel
%7    =  OpFunctionCall %1 %9 %four %five
         OpReturn
         OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardFunctionCallGood) {
  char str[] = R"(
         OpCapability Shader
         OpCapability Linkage
         OpMemoryModel Logical GLSL450
%1    =  OpTypeVoid
%2    =  OpTypeInt 32 1
%3    =  OpTypeInt 32 0
%four =  OpConstant %2 4
%five =  OpConstant %3 5
%8    =  OpTypeFunction %1 %2 %3
%4    =  OpTypeFunction %1
%5    =  OpFunction %1 None %4
%6    =  OpLabel
%7    =  OpFunctionCall %1 %9 %four %five
         OpReturn
         OpFunctionEnd
%9    =  OpFunction %1 None %8
%10   =  OpFunctionParameter %2
%11   =  OpFunctionParameter %3
%12   =  OpLabel
         OpReturn
         OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardBranchConditionalGood) {
  char str[] = R"(
            OpCapability Shader
            OpCapability Linkage
            OpMemoryModel Logical GLSL450
%voidt  =   OpTypeVoid
%boolt  =   OpTypeBool
%vfunct =   OpTypeFunction %voidt
%true   =   OpConstantTrue %boolt
%main   =   OpFunction %voidt None %vfunct
%mainl  =   OpLabel
            OpSelectionMerge %endl None
            OpBranchConditional %true %truel %falsel
%truel  =   OpLabel
            OpNop
            OpBranch %endl
%falsel =   OpLabel
            OpNop
            OpBranch %endl
%endl    =  OpLabel
            OpReturn
            OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardBranchConditionalWithWeightsGood) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
%voidt  =  OpTypeVoid
%boolt  =  OpTypeBool
%vfunct =  OpTypeFunction %voidt
%true   =  OpConstantTrue %boolt
%main   =  OpFunction %voidt None %vfunct
%mainl  =  OpLabel
           OpSelectionMerge %endl None
           OpBranchConditional %true %truel %falsel 1 9
%truel  =  OpLabel
           OpNop
           OpBranch %endl
%falsel =  OpLabel
           OpNop
           OpBranch %endl
%endl   =  OpLabel
           OpReturn
           OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardBranchConditionalNonDominantConditionBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpName %tcpy "conditional"
%voidt  =  OpTypeVoid
%boolt  =  OpTypeBool
%vfunct =  OpTypeFunction %voidt
%true   =  OpConstantTrue %boolt
%main   =  OpFunction %voidt None %vfunct
%mainl  =  OpLabel
           OpSelectionMerge %endl None
           OpBranchConditional %tcpy %truel %falsel ;
%truel  =  OpLabel
           OpNop
           OpBranch %endl
%falsel =  OpLabel
           OpNop
           OpBranch %endl
%endl   =  OpLabel
%tcpy   =  OpCopyObject %boolt %true
           OpReturn
           OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("conditional"));
}

TEST_F(ValidateSSA, ForwardBranchConditionalMissingTargetBad) {
  char str[] = R"(
           OpCapability Shader
           OpCapability Linkage
           OpMemoryModel Logical GLSL450
           OpName %missing "missing"
%voidt  =  OpTypeVoid
%boolt  =  OpTypeBool
%vfunct =  OpTypeFunction %voidt
%true   =  OpConstantTrue %boolt
%main   =  OpFunction %voidt None %vfunct
%mainl  =  OpLabel
           OpSelectionMerge %endl None
           OpBranchConditional %true %missing %falsel
%truel  =  OpLabel
           OpNop
           OpBranch %endl
%falsel =  OpLabel
           OpNop
           OpBranch %endl
%endl   =  OpLabel
           OpReturn
           OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

// Since Int8 requires the Kernel capability, the signedness of int types may
// not be "1".
const std::string kHeader = R"(
OpCapability Int8
OpCapability DeviceEnqueue
OpCapability Linkage
OpMemoryModel Logical OpenCL
)";

const std::string kBasicTypes = R"(
%voidt  =  OpTypeVoid
%boolt  =  OpTypeBool
%int8t  =  OpTypeInt 8 0
%uintt  =  OpTypeInt 32 0
%vfunct =  OpTypeFunction %voidt
%intptrt = OpTypePointer UniformConstant %uintt
%zero      = OpConstant %uintt 0
%one       = OpConstant %uintt 1
%ten       = OpConstant %uintt 10
%false     = OpConstantFalse %boolt
)";

const std::string kKernelTypesAndConstants = R"(
%queuet  = OpTypeQueue

%three   = OpConstant %uintt 3
%arr3t   = OpTypeArray %uintt %three
%ndt     = OpTypeStruct %uintt %arr3t %arr3t %arr3t

%eventt  = OpTypeEvent

%offset = OpConstant %uintt 0
%local  = OpConstant %uintt 1
%gl     = OpConstant %uintt 1

%nevent = OpConstant %uintt 0
%event  = OpConstantNull %eventt

%firstp = OpConstant %int8t 0
%psize  = OpConstant %uintt 0
%palign = OpConstant %uintt 32
%lsize  = OpConstant %uintt 1
%flags  = OpConstant %uintt 0 ; NoWait

%kfunct = OpTypeFunction %voidt %intptrt
)";

const std::string kKernelSetup = R"(
%dqueue = OpGetDefaultQueue %queuet
%ndval  = OpBuildNDRange %ndt %gl %local %offset
%revent = OpUndef %eventt

)";

const std::string kKernelDefinition = R"(
%kfunc  = OpFunction %voidt None %kfunct
%iparam = OpFunctionParameter %intptrt
%kfuncl = OpLabel
          OpNop
          OpReturn
          OpFunctionEnd
)";

TEST_F(ValidateSSA, EnqueueKernelGood) {
  std::string str = kHeader + kBasicTypes + kKernelTypesAndConstants +
                    kKernelDefinition + R"(
                %main   = OpFunction %voidt None %vfunct
                %mainl  = OpLabel
                )" + kKernelSetup + R"(
                %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event %revent %kfunc %firstp %psize
                                        %palign %lsize
                          OpReturn
                          OpFunctionEnd
                 )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, ForwardEnqueueKernelGood) {
  std::string str = kHeader + kBasicTypes + kKernelTypesAndConstants + R"(
                %main   = OpFunction %voidt None %vfunct
                %mainl  = OpLabel
                )" +
                    kKernelSetup + R"(
                %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event %revent %kfunc %firstp %psize
                                        %palign %lsize
                         OpReturn
                         OpFunctionEnd
                 )" + kKernelDefinition;
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, EnqueueMissingFunctionBad) {
  std::string str = kHeader + "OpName %kfunc \"kfunc\"" + kBasicTypes +
                    kKernelTypesAndConstants + R"(
                %main   = OpFunction %voidt None %vfunct
                %mainl  = OpLabel
                )" + kKernelSetup + R"(
                %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event %revent %kfunc %firstp %psize
                                        %palign %lsize
                         OpReturn
                         OpFunctionEnd
                 )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("kfunc"));
}

std::string forwardKernelNonDominantParameterBaseCode(
    std::string name = std::string()) {
  std::string op_name;
  if (name.empty()) {
    op_name = "";
  } else {
    op_name = "\nOpName %" + name + " \"" + name + "\"\n";
  }
  std::string out = kHeader + op_name + kBasicTypes + kKernelTypesAndConstants +
                    kKernelDefinition +
                    R"(
                %main   = OpFunction %voidt None %vfunct
                %mainl  = OpLabel
                )" + kKernelSetup;
  return out;
}

TEST_F(ValidateSSA, ForwardEnqueueKernelMissingParameter1Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("missing") + R"(
                %err    = OpEnqueueKernel %missing %dqueue %flags %ndval
                                        %nevent %event %revent %kfunc %firstp
                                        %psize %palign %lsize
                          OpReturn
                          OpFunctionEnd
                )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter2Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("dqueue2") + R"(
                %err     = OpEnqueueKernel %uintt %dqueue2 %flags %ndval
                                            %nevent %event %revent %kfunc
                                            %firstp %psize %palign %lsize
                %dqueue2 = OpGetDefaultQueue %queuet
                           OpReturn
                           OpFunctionEnd
                )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("dqueue2"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter3Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("ndval2") + R"(
                %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval2
                                        %nevent %event %revent %kfunc %firstp
                                        %psize %palign %lsize
                %ndval2  = OpBuildNDRange %ndt %gl %local %offset
                          OpReturn
                          OpFunctionEnd
                )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("ndval2"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter4Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("nevent2") + R"(
              %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent2
                                        %event %revent %kfunc %firstp %psize
                                        %palign %lsize
              %nevent2 = OpCopyObject %uintt %nevent
                        OpReturn
                        OpFunctionEnd
              )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("nevent2"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter5Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("event2") + R"(
              %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event2 %revent %kfunc %firstp %psize
                                        %palign %lsize
              %event2  = OpCopyObject %eventt %event
                         OpReturn
                         OpFunctionEnd
              )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("event2"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter6Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("revent2") + R"(
              %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event %revent2 %kfunc %firstp %psize
                                        %palign %lsize
              %revent2 = OpCopyObject %eventt %revent
                         OpReturn
                         OpFunctionEnd
              )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("revent2"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter8Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("firstp2") + R"(
              %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event %revent %kfunc %firstp2 %psize
                                        %palign %lsize
              %firstp2 = OpCopyObject %int8t %firstp
                         OpReturn
                         OpFunctionEnd
              )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("firstp2"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter9Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("psize2") + R"(
              %err    = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event %revent %kfunc %firstp %psize2
                                        %palign %lsize
              %psize2 = OpCopyObject %uintt %psize
                        OpReturn
                        OpFunctionEnd
              )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("psize2"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter10Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("palign2") + R"(
              %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event %revent %kfunc %firstp %psize
                                        %palign2 %lsize
              %palign2 = OpCopyObject %uintt %palign
                        OpReturn
                        OpFunctionEnd
              )";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("palign2"));
}

TEST_F(ValidateSSA, ForwardEnqueueKernelNonDominantParameter11Bad) {
  std::string str = forwardKernelNonDominantParameterBaseCode("lsize2") + R"(
              %err     = OpEnqueueKernel %uintt %dqueue %flags %ndval %nevent
                                        %event %revent %kfunc %firstp %psize
                                        %palign %lsize2
              %lsize2  = OpCopyObject %uintt %lsize
                         OpReturn
                         OpFunctionEnd
              )";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("lsize2"));
}

static const bool kWithNDrange = true;
static const bool kNoNDrange = false;
std::pair<std::string, bool> cases[] = {
    {"OpGetKernelNDrangeSubGroupCount", kWithNDrange},
    {"OpGetKernelNDrangeMaxSubGroupSize", kWithNDrange},
    {"OpGetKernelWorkGroupSize", kNoNDrange},
    {"OpGetKernelPreferredWorkGroupSizeMultiple", kNoNDrange}};

INSTANTIATE_TEST_CASE_P(KernelArgs, ValidateSSA, ::testing::ValuesIn(cases), );

static const std::string return_instructions = R"(
  OpReturn
  OpFunctionEnd
)";

TEST_P(ValidateSSA, GetKernelGood) {
  std::string instruction = GetParam().first;
  bool with_ndrange = GetParam().second;
  std::string ndrange_param = with_ndrange ? " %ndval " : " ";

  std::stringstream ss;
  // clang-format off
  ss << forwardKernelNonDominantParameterBaseCode() + " %numsg = "
     << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign"
     << return_instructions;
  // clang-format on

  CompileSuccessfully(ss.str());
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_P(ValidateSSA, ForwardGetKernelGood) {
  std::string instruction = GetParam().first;
  bool with_ndrange = GetParam().second;
  std::string ndrange_param = with_ndrange ? " %ndval " : " ";

  // clang-format off
  std::string str = kHeader + kBasicTypes + kKernelTypesAndConstants +
               R"(
            %main    = OpFunction %voidt None %vfunct
            %mainl   = OpLabel
                )"
            + kKernelSetup + " %numsg = "
            + instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign"
            + return_instructions + kKernelDefinition;
  // clang-format on

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_P(ValidateSSA, ForwardGetKernelMissingDefinitionBad) {
  std::string instruction = GetParam().first;
  bool with_ndrange = GetParam().second;
  std::string ndrange_param = with_ndrange ? " %ndval " : " ";

  std::stringstream ss;
  // clang-format off
  ss << forwardKernelNonDominantParameterBaseCode("missing") + " %numsg = "
     << instruction + " %uintt" + ndrange_param + "%missing %firstp %psize %palign"
     << return_instructions;
  // clang-format on

  CompileSuccessfully(ss.str());
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_P(ValidateSSA, ForwardGetKernelNDrangeSubGroupCountMissingParameter1Bad) {
  std::string instruction = GetParam().first;
  bool with_ndrange = GetParam().second;
  std::string ndrange_param = with_ndrange ? " %ndval " : " ";

  std::stringstream ss;
  // clang-format off
  ss << forwardKernelNonDominantParameterBaseCode("missing") + " %numsg = "
     << instruction + " %missing" + ndrange_param + "%kfunc %firstp %psize %palign"
     << return_instructions;
  // clang-format on

  CompileSuccessfully(ss.str());
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_P(ValidateSSA,
       ForwardGetKernelNDrangeSubGroupCountNonDominantParameter2Bad) {
  std::string instruction = GetParam().first;
  bool with_ndrange = GetParam().second;
  std::string ndrange_param = with_ndrange ? " %ndval2 " : " ";

  std::stringstream ss;
  // clang-format off
  ss << forwardKernelNonDominantParameterBaseCode("ndval2") + " %numsg = "
     << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign"
     << "\n %ndval2  = OpBuildNDRange %ndt %gl %local %offset"
     << return_instructions;
  // clang-format on

  if (GetParam().second) {
    CompileSuccessfully(ss.str());
    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
    EXPECT_THAT(getDiagnosticString(), HasSubstr("ndval2"));
  }
}

TEST_P(ValidateSSA,
       ForwardGetKernelNDrangeSubGroupCountNonDominantParameter4Bad) {
  std::string instruction = GetParam().first;
  bool with_ndrange = GetParam().second;
  std::string ndrange_param = with_ndrange ? " %ndval " : " ";

  std::stringstream ss;
  // clang-format off
  ss << forwardKernelNonDominantParameterBaseCode("firstp2") + " %numsg = "
     << instruction + " %uintt" + ndrange_param + "%kfunc %firstp2 %psize %palign"
     << "\n %firstp2 = OpCopyObject %int8t %firstp"
     << return_instructions;
  // clang-format on

  CompileSuccessfully(ss.str());
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("firstp2"));
}

TEST_P(ValidateSSA,
       ForwardGetKernelNDrangeSubGroupCountNonDominantParameter5Bad) {
  std::string instruction = GetParam().first;
  bool with_ndrange = GetParam().second;
  std::string ndrange_param = with_ndrange ? " %ndval " : " ";

  std::stringstream ss;
  // clang-format off
  ss << forwardKernelNonDominantParameterBaseCode("psize2") + " %numsg = "
     << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize2 %palign"
     << "\n %psize2  = OpCopyObject %uintt %psize"
     << return_instructions;
  // clang-format on

  CompileSuccessfully(ss.str());
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("psize2"));
}

TEST_P(ValidateSSA,
       ForwardGetKernelNDrangeSubGroupCountNonDominantParameter6Bad) {
  std::string instruction = GetParam().first;
  bool with_ndrange = GetParam().second;
  std::string ndrange_param = with_ndrange ? " %ndval " : " ";

  std::stringstream ss;
  // clang-format off
  ss << forwardKernelNonDominantParameterBaseCode("palign2") + " %numsg = "
     << instruction + " %uintt" + ndrange_param + "%kfunc %firstp %psize %palign2"
     << "\n %palign2 = OpCopyObject %uintt %palign"
     << return_instructions;
  // clang-format on

  if (GetParam().second) {
    CompileSuccessfully(ss.str());
    ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
    EXPECT_THAT(getDiagnosticString(), HasSubstr("palign2"));
  }
}

TEST_F(ValidateSSA, PhiGood) {
  std::string str = kHeader + kBasicTypes +
                    R"(
%func      = OpFunction %voidt None %vfunct
%preheader = OpLabel
%init      = OpCopyObject %uintt %zero
             OpBranch %loop
%loop      = OpLabel
%i         = OpPhi %uintt %init %preheader %loopi %loop
%loopi     = OpIAdd %uintt %i %one
             OpNop
%cond      = OpSLessThan %boolt %i %ten
             OpLoopMerge %endl %loop None
             OpBranchConditional %cond %loop %endl
%endl      = OpLabel
             OpReturn
             OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, PhiMissingTypeBad) {
  std::string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
                    R"(
%func      = OpFunction %voidt None %vfunct
%preheader = OpLabel
%init      = OpCopyObject %uintt %zero
             OpBranch %loop
%loop      = OpLabel
%i         = OpPhi %missing %init %preheader %loopi %loop
%loopi     = OpIAdd %uintt %i %one
             OpNop
%cond      = OpSLessThan %boolt %i %ten
             OpLoopMerge %endl %loop None
             OpBranchConditional %cond %loop %endl
%endl      = OpLabel
             OpReturn
             OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, PhiMissingIdBad) {
  std::string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
                    R"(
%func      = OpFunction %voidt None %vfunct
%preheader = OpLabel
%init      = OpCopyObject %uintt %zero
             OpBranch %loop
%loop      = OpLabel
%i         = OpPhi %uintt %missing %preheader %loopi %loop
%loopi     = OpIAdd %uintt %i %one
             OpNop
%cond      = OpSLessThan %boolt %i %ten
             OpLoopMerge %endl %loop None
             OpBranchConditional %cond %loop %endl
%endl      = OpLabel
             OpReturn
             OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, PhiMissingLabelBad) {
  std::string str = kHeader + "OpName %missing \"missing\"" + kBasicTypes +
                    R"(
%func      = OpFunction %voidt None %vfunct
%preheader = OpLabel
%init      = OpCopyObject %uintt %zero
             OpBranch %loop
%loop      = OpLabel
%i         = OpPhi %uintt %init %missing %loopi %loop
%loopi     = OpIAdd %uintt %i %one
             OpNop
%cond      = OpSLessThan %boolt %i %ten
             OpLoopMerge %endl %loop None
             OpBranchConditional %cond %loop %endl
%endl      = OpLabel
             OpReturn
             OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), HasSubstr("missing"));
}

TEST_F(ValidateSSA, IdDominatesItsUseGood) {
  std::string str = kHeader + kBasicTypes +
                    R"(
%func      = OpFunction %voidt None %vfunct
%entry     = OpLabel
%cond      = OpSLessThan %boolt %one %ten
%eleven    = OpIAdd %uintt %one %ten
             OpSelectionMerge %merge None
             OpBranchConditional %cond %t %f
%t         = OpLabel
%twelve    = OpIAdd %uintt %eleven %one
             OpBranch %merge
%f         = OpLabel
%twentytwo = OpIAdd %uintt %eleven %ten
             OpBranch %merge
%merge     = OpLabel
             OpReturn
             OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, IdDoesNotDominateItsUseBad) {
  std::string str = kHeader +
                    "OpName %eleven \"eleven\"\n"
                    "OpName %true_block \"true_block\"\n"
                    "OpName %false_block \"false_block\"" +
                    kBasicTypes +
                    R"(
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
%cond        = OpSLessThan %boolt %one %ten
               OpSelectionMerge %merge None
               OpBranchConditional %cond %true_block %false_block
%true_block  = OpLabel
%eleven      = OpIAdd %uintt %one %ten
%twelve      = OpIAdd %uintt %eleven %one
               OpBranch %merge
%false_block = OpLabel
%twentytwo   = OpIAdd %uintt %eleven %ten
               OpBranch %merge
%merge       = OpLabel
               OpReturn
               OpFunctionEnd
)";
  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      MatchesRegex("ID .\\[%eleven\\] defined in block .\\[%true_block\\] "
                   "does not dominate its use in block .\\[%false_block\\]\n"
                   "  %false_block = OpLabel\n"));
}

TEST_F(ValidateSSA, PhiUseDoesntDominateDefinitionGood) {
  std::string str = kHeader + kBasicTypes +
                    R"(
%funcintptrt = OpTypePointer Function %uintt
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
%var_one     = OpVariable %funcintptrt Function %one
%one_val     = OpLoad %uintt %var_one
               OpBranch %loop
%loop        = OpLabel
%i           = OpPhi %uintt %one_val %entry %inew %cont
%cond        = OpSLessThan %boolt %one %ten
               OpLoopMerge %merge %cont None
               OpBranchConditional %cond %body %merge
%body        = OpLabel
               OpBranch %cont
%cont        = OpLabel
%inew        = OpIAdd %uintt %i %one
               OpBranch %loop
%merge       = OpLabel
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA,
       PhiUseDoesntDominateUseOfPhiOperandUsedBeforeDefinitionBad) {
  std::string str = kHeader + "OpName %inew \"inew\"" + kBasicTypes +
                    R"(
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
%var_one     = OpVariable %intptrt Function %one
%one_val     = OpLoad %uintt %var_one
               OpBranch %loop
%loop        = OpLabel
%i           = OpPhi %uintt %one_val %entry %inew %cont
%bad         = OpIAdd %uintt %inew %one
%cond        = OpSLessThan %boolt %one %ten
               OpLoopMerge %merge %cont None
               OpBranchConditional %cond %body %merge
%body        = OpLabel
               OpBranch %cont
%cont        = OpLabel
%inew        = OpIAdd %uintt %i %one
               OpBranch %loop
%merge       = OpLabel
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              MatchesRegex("ID .\\[%inew\\] has not been defined\n"
                           "  %19 = OpIAdd %uint %inew %uint_1\n"));
}

TEST_F(ValidateSSA, PhiUseMayComeFromNonDominatingBlockGood) {
  std::string str = kHeader + "OpName %if_true \"if_true\"\n" +
                    "OpName %exit \"exit\"\n" + "OpName %copy \"copy\"\n" +
                    kBasicTypes +
                    R"(
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
               OpBranchConditional %false %if_true %exit

%if_true     = OpLabel
%copy        = OpCopyObject %boolt %false
               OpBranch %exit

; The use of %copy here is ok, even though it was defined
; in a block that does not dominate %exit.  That's the point
; of an OpPhi.
%exit        = OpLabel
%value       = OpPhi %boolt %false %entry %copy %if_true
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
}

TEST_F(ValidateSSA, PhiUsesItsOwnDefinitionGood) {
  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/415
  //
  // Non-phi instructions can't use their own definitions, as
  // already checked in test DominateUsageSameInstructionBad.
  std::string str = kHeader + "OpName %loop \"loop\"\n" +
                    "OpName %value \"value\"\n" + kBasicTypes +
                    R"(
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
               OpBranch %loop

%loop        = OpLabel
%value       = OpPhi %boolt %false %entry %value %loop
               OpBranch %loop

               OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
}

TEST_F(ValidateSSA, PhiVariableDefNotDominatedByParentBlockBad) {
  std::string str = kHeader + "OpName %if_true \"if_true\"\n" +
                    "OpName %if_false \"if_false\"\n" +
                    "OpName %exit \"exit\"\n" + "OpName %value \"phi\"\n" +
                    "OpName %true_copy \"true_copy\"\n" +
                    "OpName %false_copy \"false_copy\"\n" + kBasicTypes +
                    R"(
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
               OpBranchConditional %false %if_true %if_false

%if_true     = OpLabel
%true_copy   = OpCopyObject %boolt %false
               OpBranch %exit

%if_false    = OpLabel
%false_copy  = OpCopyObject %boolt %false
               OpBranch %exit

; The (variable,Id) pairs are swapped.
%exit        = OpLabel
%value       = OpPhi %boolt %true_copy %if_false %false_copy %if_true
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      MatchesRegex("In OpPhi instruction .\\[%phi\\], ID .\\[%true_copy\\] "
                   "definition does not dominate its parent .\\[%if_false\\]\n"
                   "  %phi = OpPhi %bool %true_copy %if_false %false_copy "
                   "%if_true\n"));
}

TEST_F(ValidateSSA, PhiVariableDefDominatesButNotDefinedInParentBlock) {
  std::string str = kHeader + "OpName %if_true \"if_true\"\n" + kBasicTypes +
                    R"(
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
               OpBranchConditional %false %if_true %if_false

%if_true     = OpLabel
%true_copy   = OpCopyObject %boolt %false
               OpBranch %if_tnext
%if_tnext    = OpLabel
               OpBranch %exit

%if_false    = OpLabel
%false_copy  = OpCopyObject %boolt %false
               OpBranch %if_fnext
%if_fnext    = OpLabel
               OpBranch %exit

%exit        = OpLabel
%value       = OpPhi %boolt %true_copy %if_tnext %false_copy %if_fnext
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA,
       DominanceCheckIgnoresUsesInUnreachableBlocksDefInBlockGood) {
  std::string str = kHeader + kBasicTypes +
                    R"(
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
%def         = OpCopyObject %boolt %false
               OpReturn

%unreach     = OpLabel
%use         = OpCopyObject %boolt %def
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(str);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
}

TEST_F(ValidateSSA, PhiVariableUnreachableDefNotInParentBlock) {
  std::string str = kHeader + "OpName %unreachable \"unreachable\"\n" +
                    kBasicTypes +
                    R"(
%func        = OpFunction %voidt None %vfunct
%entry       = OpLabel
               OpBranch %if_false

%unreachable = OpLabel
%copy        = OpCopyObject %boolt %false
               OpBranch %if_tnext
%if_tnext    = OpLabel
               OpBranch %exit

%if_false    = OpLabel
%false_copy  = OpCopyObject %boolt %false
               OpBranch %if_fnext
%if_fnext    = OpLabel
               OpBranch %exit

%exit        = OpLabel
%value       = OpPhi %boolt %copy %if_tnext %false_copy %if_fnext
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA,
       DominanceCheckIgnoresUsesInUnreachableBlocksDefIsParamGood) {
  std::string str = kHeader + kBasicTypes +
                    R"(
%void_fn_int = OpTypeFunction %voidt %uintt
%func        = OpFunction %voidt None %void_fn_int
%int_param   = OpFunctionParameter %uintt
%entry       = OpLabel
               OpReturn

%unreach     = OpLabel
%use         = OpCopyObject %uintt %int_param
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(str);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions()) << getDiagnosticString();
}

TEST_F(ValidateSSA, UseFunctionParameterFromOtherFunctionBad) {
  std::string str = kHeader +
                    "OpName %first \"first\"\n"
                    "OpName %func \"func\"\n" +
                    "OpName %func2 \"func2\"\n" + kBasicTypes +
                    R"(
%viifunct  = OpTypeFunction %voidt %uintt %uintt
%func      = OpFunction %voidt None %viifunct
%first     = OpFunctionParameter %uintt
%second    = OpFunctionParameter %uintt
             OpFunctionEnd
%func2     = OpFunction %voidt None %viifunct
%first2    = OpFunctionParameter %uintt
%second2   = OpFunctionParameter %uintt
%entry2    = OpLabel
%baduse    = OpIAdd %uintt %first %first2
             OpReturn
             OpFunctionEnd
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      MatchesRegex("ID .\\[%first\\] used in function .\\[%func2\\] is used "
                   "outside of it's defining function .\\[%func\\]\n"
                   "  %func = OpFunction %void None %14\n"));
}

TEST_F(ValidateSSA, TypeForwardPointerForwardReference) {
  // See https://github.com/KhronosGroup/SPIRV-Tools/issues/429
  //
  // ForwardPointers can references instructions that have not been defined
  std::string str = R"(
               OpCapability Kernel
               OpCapability Addresses
               OpCapability Linkage
               OpMemoryModel Logical OpenCL
               OpName %intptrt "intptrt"
               OpTypeForwardPointer %intptrt UniformConstant
       %uint = OpTypeInt 32 0
    %intptrt = OpTypePointer UniformConstant %uint
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateSSA, TypeStructForwardReference) {
  std::string str = R"(
               OpCapability Kernel
               OpCapability Addresses
               OpCapability Linkage
               OpMemoryModel Logical OpenCL
               OpName %structptr "structptr"
               OpTypeForwardPointer %structptr UniformConstant
       %uint = OpTypeInt 32 0
   %structt1 = OpTypeStruct %structptr %uint
   %structt2 = OpTypeStruct %uint %structptr
   %structt3 = OpTypeStruct %uint %uint %structptr
   %structt4 = OpTypeStruct %uint %uint %uint %structptr
  %structptr = OpTypePointer UniformConstant %structt1
)";

  CompileSuccessfully(str);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

// TODO(umar): OpGroupMemberDecorate

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