// Copyright (c) 2018 Google LLC.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <memory>
#include <set>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>

#include "gmock/gmock.h"
#include "source/opt/iterator.h"
#include "source/opt/loop_dependence.h"
#include "source/opt/loop_descriptor.h"
#include "source/opt/pass.h"
#include "source/opt/tree_iterator.h"
#include "test/opt//assembly_builder.h"
#include "test/opt//function_utils.h"
#include "test/opt//pass_fixture.h"
#include "test/opt//pass_utils.h"

namespace spvtools {
namespace opt {
namespace {

using DependencyAnalysis = ::testing::Test;

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
void main(){
  int[10] arr;
  int[10] arr2;
  int a = 2;
  for (int i = 0; i < 10; i++) {
    arr[a] = arr[3];
    arr[a*2] = arr[a+3];
    arr[6] = arr2[6];
    arr[a+5] = arr2[7];
  }
}
*/
TEST(DependencyAnalysis, ZIV) {
  const std::string text = R"(               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %25 "arr"
               OpName %39 "arr2"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 2
         %11 = OpConstant %6 0
         %18 = OpConstant %6 10
         %19 = OpTypeBool
         %21 = OpTypeInt 32 0
         %22 = OpConstant %21 10
         %23 = OpTypeArray %6 %22
         %24 = OpTypePointer Function %23
         %27 = OpConstant %6 3
         %38 = OpConstant %6 6
         %44 = OpConstant %6 5
         %46 = OpConstant %6 7
         %51 = OpConstant %6 1
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %25 = OpVariable %24 Function
         %39 = OpVariable %24 Function
               OpBranch %12
         %12 = OpLabel
         %53 = OpPhi %6 %11 %5 %52 %15
               OpLoopMerge %14 %15 None
               OpBranch %16
         %16 = OpLabel
         %20 = OpSLessThan %19 %53 %18
               OpBranchConditional %20 %13 %14
         %13 = OpLabel
         %28 = OpAccessChain %7 %25 %27
         %29 = OpLoad %6 %28
         %30 = OpAccessChain %7 %25 %9
               OpStore %30 %29
         %32 = OpIMul %6 %9 %9
         %34 = OpIAdd %6 %9 %27
         %35 = OpAccessChain %7 %25 %34
         %36 = OpLoad %6 %35
         %37 = OpAccessChain %7 %25 %32
               OpStore %37 %36
         %40 = OpAccessChain %7 %39 %38
         %41 = OpLoad %6 %40
         %42 = OpAccessChain %7 %25 %38
               OpStore %42 %41
         %45 = OpIAdd %6 %9 %44
         %47 = OpAccessChain %7 %39 %46
         %48 = OpLoad %6 %47
         %49 = OpAccessChain %7 %25 %45
               OpStore %49 %48
               OpBranch %15
         %15 = OpLabel
         %52 = OpIAdd %6 %53 %51
               OpBranch %12
         %14 = OpLabel
               OpReturn
               OpFunctionEnd
)";
  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  const Function* f = spvtest::GetFunction(module, 4);
  LoopDescriptor& ld = *context->GetLoopDescriptor(f);

  Loop* loop = &ld.GetLoopByIndex(0);
  std::vector<const Loop*> loops{loop};
  LoopDependenceAnalysis analysis{context.get(), loops};

  const Instruction* store[4];
  int stores_found = 0;
  for (const Instruction& inst : *spvtest::GetBasicBlock(f, 13)) {
    if (inst.opcode() == spv::Op::OpStore) {
      store[stores_found] = &inst;
      ++stores_found;
    }
  }

  for (int i = 0; i < 4; ++i) {
    EXPECT_TRUE(store[i]);
  }

  // 29 -> 30 tests looking through constants.
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(29),
                                       store[0], &distance_vector));
  }

  // 36 -> 37 tests looking through additions.
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(36),
                                       store[1], &distance_vector));
  }

  // 41 -> 42 tests looking at same index across two different arrays.
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(41),
                                       store[2], &distance_vector));
  }

  // 48 -> 49 tests looking through additions for same index in two different
  // arrays.
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(48),
                                       store[3], &distance_vector));
  }
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
layout(location = 0) in vec4 c;
void main(){
  int[10] arr;
  int[10] arr2;
  int[10] arr3;
  int[10] arr4;
  int[10] arr5;
  int N = int(c.x);
  for (int i = 0; i < N; i++) {
    arr[2*N] = arr[N];
    arr2[2*N+1] = arr2[N];
    arr3[2*N] = arr3[N-1];
    arr4[N] = arr5[N];
  }
}
*/
TEST(DependencyAnalysis, SymbolicZIV) {
  const std::string text = R"(               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main" %12
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %12 "c"
               OpName %33 "arr"
               OpName %41 "arr2"
               OpName %50 "arr3"
               OpName %58 "arr4"
               OpName %60 "arr5"
               OpDecorate %12 Location 0
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpTypeFloat 32
         %10 = OpTypeVector %9 4
         %11 = OpTypePointer Input %10
         %12 = OpVariable %11 Input
         %13 = OpTypeInt 32 0
         %14 = OpConstant %13 0
         %15 = OpTypePointer Input %9
         %20 = OpConstant %6 0
         %28 = OpTypeBool
         %30 = OpConstant %13 10
         %31 = OpTypeArray %6 %30
         %32 = OpTypePointer Function %31
         %34 = OpConstant %6 2
         %44 = OpConstant %6 1
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %33 = OpVariable %32 Function
         %41 = OpVariable %32 Function
         %50 = OpVariable %32 Function
         %58 = OpVariable %32 Function
         %60 = OpVariable %32 Function
         %16 = OpAccessChain %15 %12 %14
         %17 = OpLoad %9 %16
         %18 = OpConvertFToS %6 %17
               OpBranch %21
         %21 = OpLabel
         %67 = OpPhi %6 %20 %5 %66 %24
               OpLoopMerge %23 %24 None
               OpBranch %25
         %25 = OpLabel
         %29 = OpSLessThan %28 %67 %18
               OpBranchConditional %29 %22 %23
         %22 = OpLabel
         %36 = OpIMul %6 %34 %18
         %38 = OpAccessChain %7 %33 %18
         %39 = OpLoad %6 %38
         %40 = OpAccessChain %7 %33 %36
               OpStore %40 %39
         %43 = OpIMul %6 %34 %18
         %45 = OpIAdd %6 %43 %44
         %47 = OpAccessChain %7 %41 %18
         %48 = OpLoad %6 %47
         %49 = OpAccessChain %7 %41 %45
               OpStore %49 %48
         %52 = OpIMul %6 %34 %18
         %54 = OpISub %6 %18 %44
         %55 = OpAccessChain %7 %50 %54
         %56 = OpLoad %6 %55
         %57 = OpAccessChain %7 %50 %52
               OpStore %57 %56
         %62 = OpAccessChain %7 %60 %18
         %63 = OpLoad %6 %62
         %64 = OpAccessChain %7 %58 %18
               OpStore %64 %63
               OpBranch %24
         %24 = OpLabel
         %66 = OpIAdd %6 %67 %44
               OpBranch %21
         %23 = OpLabel
               OpReturn
               OpFunctionEnd
)";
  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  const Function* f = spvtest::GetFunction(module, 4);
  LoopDescriptor& ld = *context->GetLoopDescriptor(f);

  Loop* loop = &ld.GetLoopByIndex(0);
  std::vector<const Loop*> loops{loop};
  LoopDependenceAnalysis analysis{context.get(), loops};

  const Instruction* store[4];
  int stores_found = 0;
  for (const Instruction& inst : *spvtest::GetBasicBlock(f, 22)) {
    if (inst.opcode() == spv::Op::OpStore) {
      store[stores_found] = &inst;
      ++stores_found;
    }
  }

  for (int i = 0; i < 4; ++i) {
    EXPECT_TRUE(store[i]);
  }

  // independent due to loop bounds (won't enter if N <= 0).
  // 39 -> 40 tests looking through symbols and multiplicaiton.
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(39),
                                       store[0], &distance_vector));
  }

  // 48 -> 49 tests looking through symbols and multiplication + addition.
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(48),
                                       store[1], &distance_vector));
  }

  // 56 -> 57 tests looking through symbols and arithmetic on load and store.
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(56),
                                       store[2], &distance_vector));
  }

  // independent as different arrays
  // 63 -> 64 tests looking through symbols and load/store from/to different
  // arrays.
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(63),
                                       store[3], &distance_vector));
  }
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
void a(){
  int[10] arr;
  int[11] arr2;
  int[20] arr3;
  int[20] arr4;
  int a = 2;
  for (int i = 0; i < 10; i++) {
    arr[i] = arr[i];
    arr2[i] = arr2[i+1];
    arr3[i] = arr3[i-1];
    arr4[2*i] = arr4[i];
  }
}
void b(){
  int[10] arr;
  int[11] arr2;
  int[20] arr3;
  int[20] arr4;
  int a = 2;
  for (int i = 10; i > 0; i--) {
    arr[i] = arr[i];
    arr2[i] = arr2[i+1];
    arr3[i] = arr3[i-1];
    arr4[2*i] = arr4[i];
  }
}

void main() {
  a();
  b();
}
*/
TEST(DependencyAnalysis, SIV) {
  const std::string text = R"(               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %6 "a("
               OpName %8 "b("
               OpName %12 "a"
               OpName %14 "i"
               OpName %29 "arr"
               OpName %38 "arr2"
               OpName %49 "arr3"
               OpName %56 "arr4"
               OpName %65 "a"
               OpName %66 "i"
               OpName %74 "arr"
               OpName %80 "arr2"
               OpName %87 "arr3"
               OpName %94 "arr4"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeInt 32 1
         %11 = OpTypePointer Function %10
         %13 = OpConstant %10 2
         %15 = OpConstant %10 0
         %22 = OpConstant %10 10
         %23 = OpTypeBool
         %25 = OpTypeInt 32 0
         %26 = OpConstant %25 10
         %27 = OpTypeArray %10 %26
         %28 = OpTypePointer Function %27
         %35 = OpConstant %25 11
         %36 = OpTypeArray %10 %35
         %37 = OpTypePointer Function %36
         %41 = OpConstant %10 1
         %46 = OpConstant %25 20
         %47 = OpTypeArray %10 %46
         %48 = OpTypePointer Function %47
          %4 = OpFunction %2 None %3
          %5 = OpLabel
        %103 = OpFunctionCall %2 %6
        %104 = OpFunctionCall %2 %8
               OpReturn
               OpFunctionEnd
          %6 = OpFunction %2 None %3
          %7 = OpLabel
         %12 = OpVariable %11 Function
         %14 = OpVariable %11 Function
         %29 = OpVariable %28 Function
         %38 = OpVariable %37 Function
         %49 = OpVariable %48 Function
         %56 = OpVariable %48 Function
               OpStore %12 %13
               OpStore %14 %15
               OpBranch %16
         %16 = OpLabel
        %105 = OpPhi %10 %15 %7 %64 %19
               OpLoopMerge %18 %19 None
               OpBranch %20
         %20 = OpLabel
         %24 = OpSLessThan %23 %105 %22
               OpBranchConditional %24 %17 %18
         %17 = OpLabel
         %32 = OpAccessChain %11 %29 %105
         %33 = OpLoad %10 %32
         %34 = OpAccessChain %11 %29 %105
               OpStore %34 %33
         %42 = OpIAdd %10 %105 %41
         %43 = OpAccessChain %11 %38 %42
         %44 = OpLoad %10 %43
         %45 = OpAccessChain %11 %38 %105
               OpStore %45 %44
         %52 = OpISub %10 %105 %41
         %53 = OpAccessChain %11 %49 %52
         %54 = OpLoad %10 %53
         %55 = OpAccessChain %11 %49 %105
               OpStore %55 %54
         %58 = OpIMul %10 %13 %105
         %60 = OpAccessChain %11 %56 %105
         %61 = OpLoad %10 %60
         %62 = OpAccessChain %11 %56 %58
               OpStore %62 %61
               OpBranch %19
         %19 = OpLabel
         %64 = OpIAdd %10 %105 %41
               OpStore %14 %64
               OpBranch %16
         %18 = OpLabel
               OpReturn
               OpFunctionEnd
          %8 = OpFunction %2 None %3
          %9 = OpLabel
         %65 = OpVariable %11 Function
         %66 = OpVariable %11 Function
         %74 = OpVariable %28 Function
         %80 = OpVariable %37 Function
         %87 = OpVariable %48 Function
         %94 = OpVariable %48 Function
               OpStore %65 %13
               OpStore %66 %22
               OpBranch %67
         %67 = OpLabel
        %106 = OpPhi %10 %22 %9 %102 %70
               OpLoopMerge %69 %70 None
               OpBranch %71
         %71 = OpLabel
         %73 = OpSGreaterThan %23 %106 %15
               OpBranchConditional %73 %68 %69
         %68 = OpLabel
         %77 = OpAccessChain %11 %74 %106
         %78 = OpLoad %10 %77
         %79 = OpAccessChain %11 %74 %106
               OpStore %79 %78
         %83 = OpIAdd %10 %106 %41
         %84 = OpAccessChain %11 %80 %83
         %85 = OpLoad %10 %84
         %86 = OpAccessChain %11 %80 %106
               OpStore %86 %85
         %90 = OpISub %10 %106 %41
         %91 = OpAccessChain %11 %87 %90
         %92 = OpLoad %10 %91
         %93 = OpAccessChain %11 %87 %106
               OpStore %93 %92
         %96 = OpIMul %10 %13 %106
         %98 = OpAccessChain %11 %94 %106
         %99 = OpLoad %10 %98
        %100 = OpAccessChain %11 %94 %96
               OpStore %100 %99
               OpBranch %70
         %70 = OpLabel
        %102 = OpISub %10 %106 %41
               OpStore %66 %102
               OpBranch %67
         %69 = OpLabel
               OpReturn
               OpFunctionEnd
)";
  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  // For the loop in function a.
  {
    const Function* f = spvtest::GetFunction(module, 6);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[4];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 17)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 4; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // = dependence
    // 33 -> 34 tests looking at SIV in same array.
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(33), store[0], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::DISTANCE);
      EXPECT_EQ(distance_vector.GetEntries()[0].direction,
                DistanceEntry::Directions::EQ);
      EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
    }

    // > -1 dependence
    // 44 -> 45 tests looking at SIV in same array with addition.
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(44), store[1], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::DISTANCE);
      EXPECT_EQ(distance_vector.GetEntries()[0].direction,
                DistanceEntry::Directions::GT);
      EXPECT_EQ(distance_vector.GetEntries()[0].distance, -1);
    }

    // < 1 dependence
    // 54 -> 55 tests looking at SIV in same array with subtraction.
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(54), store[2], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::DISTANCE);
      EXPECT_EQ(distance_vector.GetEntries()[0].direction,
                DistanceEntry::Directions::LT);
      EXPECT_EQ(distance_vector.GetEntries()[0].distance, 1);
    }

    // <=> dependence
    // 61 -> 62 tests looking at SIV in same array with multiplication.
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(61), store[3], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::UNKNOWN);
      EXPECT_EQ(distance_vector.GetEntries()[0].direction,
                DistanceEntry::Directions::ALL);
    }
  }
  // For the loop in function b.
  {
    const Function* f = spvtest::GetFunction(module, 8);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[4];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 68)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 4; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // = dependence
    // 78 -> 79 tests looking at SIV in same array.
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(78), store[0], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::DISTANCE);
      EXPECT_EQ(distance_vector.GetEntries()[0].direction,
                DistanceEntry::Directions::EQ);
      EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
    }

    // < 1 dependence
    // 85 -> 86 tests looking at SIV in same array with addition.
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(85), store[1], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::DISTANCE);
      EXPECT_EQ(distance_vector.GetEntries()[0].direction,
                DistanceEntry::Directions::LT);
      EXPECT_EQ(distance_vector.GetEntries()[0].distance, 1);
    }

    // > -1 dependence
    // 92 -> 93 tests looking at SIV in same array with subtraction.
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(92), store[2], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::DISTANCE);
      EXPECT_EQ(distance_vector.GetEntries()[0].direction,
                DistanceEntry::Directions::GT);
      EXPECT_EQ(distance_vector.GetEntries()[0].distance, -1);
    }

    // <=> dependence
    // 99 -> 100 tests looking at SIV in same array with multiplication.
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(99), store[3], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::UNKNOWN);
      EXPECT_EQ(distance_vector.GetEntries()[0].direction,
                DistanceEntry::Directions::ALL);
    }
  }
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
layout(location = 0) in vec4 c;
void a() {
  int[13] arr;
  int[15] arr2;
  int[18] arr3;
  int[18] arr4;
  int N = int(c.x);
  int C = 2;
  int a = 2;
  for (int i = 0; i < N; i++) { // Bounds are N - 1
    arr[i+2*N] = arr[i+N]; // |distance| = N
    arr2[i+N] = arr2[i+2*N] + C; // |distance| = N
    arr3[2*i+2*N+1] = arr3[2*i+N+1]; // |distance| = N
    arr4[a*i+N+1] = arr4[a*i+2*N+1]; // |distance| = N
  }
}
void b() {
  int[13] arr;
  int[15] arr2;
  int[18] arr3;
  int[18] arr4;
  int N = int(c.x);
  int C = 2;
  int a = 2;
  for (int i = N; i > 0; i--) { // Bounds are N - 1
    arr[i+2*N] = arr[i+N]; // |distance| = N
    arr2[i+N] = arr2[i+2*N] + C; // |distance| = N
    arr3[2*i+2*N+1] = arr3[2*i+N+1]; // |distance| = N
    arr4[a*i+N+1] = arr4[a*i+2*N+1]; // |distance| = N
  }
}
void main(){
  a();
  b();
}*/
TEST(DependencyAnalysis, SymbolicSIV) {
  const std::string text = R"(               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main" %16
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %6 "a("
               OpName %8 "b("
               OpName %12 "N"
               OpName %16 "c"
               OpName %23 "C"
               OpName %25 "a"
               OpName %26 "i"
               OpName %40 "arr"
               OpName %54 "arr2"
               OpName %70 "arr3"
               OpName %86 "arr4"
               OpName %105 "N"
               OpName %109 "C"
               OpName %110 "a"
               OpName %111 "i"
               OpName %120 "arr"
               OpName %131 "arr2"
               OpName %144 "arr3"
               OpName %159 "arr4"
               OpDecorate %16 Location 0
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeInt 32 1
         %11 = OpTypePointer Function %10
         %13 = OpTypeFloat 32
         %14 = OpTypeVector %13 4
         %15 = OpTypePointer Input %14
         %16 = OpVariable %15 Input
         %17 = OpTypeInt 32 0
         %18 = OpConstant %17 0
         %19 = OpTypePointer Input %13
         %24 = OpConstant %10 2
         %27 = OpConstant %10 0
         %35 = OpTypeBool
         %37 = OpConstant %17 13
         %38 = OpTypeArray %10 %37
         %39 = OpTypePointer Function %38
         %51 = OpConstant %17 15
         %52 = OpTypeArray %10 %51
         %53 = OpTypePointer Function %52
         %67 = OpConstant %17 18
         %68 = OpTypeArray %10 %67
         %69 = OpTypePointer Function %68
         %76 = OpConstant %10 1
          %4 = OpFunction %2 None %3
          %5 = OpLabel
        %178 = OpFunctionCall %2 %6
        %179 = OpFunctionCall %2 %8
               OpReturn
               OpFunctionEnd
          %6 = OpFunction %2 None %3
          %7 = OpLabel
         %12 = OpVariable %11 Function
         %23 = OpVariable %11 Function
         %25 = OpVariable %11 Function
         %26 = OpVariable %11 Function
         %40 = OpVariable %39 Function
         %54 = OpVariable %53 Function
         %70 = OpVariable %69 Function
         %86 = OpVariable %69 Function
         %20 = OpAccessChain %19 %16 %18
         %21 = OpLoad %13 %20
         %22 = OpConvertFToS %10 %21
               OpStore %12 %22
               OpStore %23 %24
               OpStore %25 %24
               OpStore %26 %27
               OpBranch %28
         %28 = OpLabel
        %180 = OpPhi %10 %27 %7 %104 %31
               OpLoopMerge %30 %31 None
               OpBranch %32
         %32 = OpLabel
         %36 = OpSLessThan %35 %180 %22
               OpBranchConditional %36 %29 %30
         %29 = OpLabel
         %43 = OpIMul %10 %24 %22
         %44 = OpIAdd %10 %180 %43
         %47 = OpIAdd %10 %180 %22
         %48 = OpAccessChain %11 %40 %47
         %49 = OpLoad %10 %48
         %50 = OpAccessChain %11 %40 %44
               OpStore %50 %49
         %57 = OpIAdd %10 %180 %22
         %60 = OpIMul %10 %24 %22
         %61 = OpIAdd %10 %180 %60
         %62 = OpAccessChain %11 %54 %61
         %63 = OpLoad %10 %62
         %65 = OpIAdd %10 %63 %24
         %66 = OpAccessChain %11 %54 %57
               OpStore %66 %65
         %72 = OpIMul %10 %24 %180
         %74 = OpIMul %10 %24 %22
         %75 = OpIAdd %10 %72 %74
         %77 = OpIAdd %10 %75 %76
         %79 = OpIMul %10 %24 %180
         %81 = OpIAdd %10 %79 %22
         %82 = OpIAdd %10 %81 %76
         %83 = OpAccessChain %11 %70 %82
         %84 = OpLoad %10 %83
         %85 = OpAccessChain %11 %70 %77
               OpStore %85 %84
         %89 = OpIMul %10 %24 %180
         %91 = OpIAdd %10 %89 %22
         %92 = OpIAdd %10 %91 %76
         %95 = OpIMul %10 %24 %180
         %97 = OpIMul %10 %24 %22
         %98 = OpIAdd %10 %95 %97
         %99 = OpIAdd %10 %98 %76
        %100 = OpAccessChain %11 %86 %99
        %101 = OpLoad %10 %100
        %102 = OpAccessChain %11 %86 %92
               OpStore %102 %101
               OpBranch %31
         %31 = OpLabel
        %104 = OpIAdd %10 %180 %76
               OpStore %26 %104
               OpBranch %28
         %30 = OpLabel
               OpReturn
               OpFunctionEnd
          %8 = OpFunction %2 None %3
          %9 = OpLabel
        %105 = OpVariable %11 Function
        %109 = OpVariable %11 Function
        %110 = OpVariable %11 Function
        %111 = OpVariable %11 Function
        %120 = OpVariable %39 Function
        %131 = OpVariable %53 Function
        %144 = OpVariable %69 Function
        %159 = OpVariable %69 Function
        %106 = OpAccessChain %19 %16 %18
        %107 = OpLoad %13 %106
        %108 = OpConvertFToS %10 %107
               OpStore %105 %108
               OpStore %109 %24
               OpStore %110 %24
               OpStore %111 %108
               OpBranch %113
        %113 = OpLabel
        %181 = OpPhi %10 %108 %9 %177 %116
               OpLoopMerge %115 %116 None
               OpBranch %117
        %117 = OpLabel
        %119 = OpSGreaterThan %35 %181 %27
               OpBranchConditional %119 %114 %115
        %114 = OpLabel
        %123 = OpIMul %10 %24 %108
        %124 = OpIAdd %10 %181 %123
        %127 = OpIAdd %10 %181 %108
        %128 = OpAccessChain %11 %120 %127
        %129 = OpLoad %10 %128
        %130 = OpAccessChain %11 %120 %124
               OpStore %130 %129
        %134 = OpIAdd %10 %181 %108
        %137 = OpIMul %10 %24 %108
        %138 = OpIAdd %10 %181 %137
        %139 = OpAccessChain %11 %131 %138
        %140 = OpLoad %10 %139
        %142 = OpIAdd %10 %140 %24
        %143 = OpAccessChain %11 %131 %134
               OpStore %143 %142
        %146 = OpIMul %10 %24 %181
        %148 = OpIMul %10 %24 %108
        %149 = OpIAdd %10 %146 %148
        %150 = OpIAdd %10 %149 %76
        %152 = OpIMul %10 %24 %181
        %154 = OpIAdd %10 %152 %108
        %155 = OpIAdd %10 %154 %76
        %156 = OpAccessChain %11 %144 %155
        %157 = OpLoad %10 %156
        %158 = OpAccessChain %11 %144 %150
               OpStore %158 %157
        %162 = OpIMul %10 %24 %181
        %164 = OpIAdd %10 %162 %108
        %165 = OpIAdd %10 %164 %76
        %168 = OpIMul %10 %24 %181
        %170 = OpIMul %10 %24 %108
        %171 = OpIAdd %10 %168 %170
        %172 = OpIAdd %10 %171 %76
        %173 = OpAccessChain %11 %159 %172
        %174 = OpLoad %10 %173
        %175 = OpAccessChain %11 %159 %165
               OpStore %175 %174
               OpBranch %116
        %116 = OpLabel
        %177 = OpISub %10 %181 %76
               OpStore %111 %177
               OpBranch %113
        %115 = OpLabel
               OpReturn
               OpFunctionEnd
)";
  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  // For the loop in function a.
  {
    const Function* f = spvtest::GetFunction(module, 6);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[4];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 29)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 4; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // independent due to loop bounds (won't enter when N <= 0)
    // 49 -> 50 tests looking through SIV and symbols with multiplication
    {
      DistanceVector distance_vector{loops.size()};
      // Independent but not yet supported.
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(49), store[0], &distance_vector));
    }

    // 63 -> 66 tests looking through SIV and symbols with multiplication and +
    // C
    {
      DistanceVector distance_vector{loops.size()};
      // Independent.
      EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(63),
                                         store[1], &distance_vector));
    }

    // 84 -> 85 tests looking through arithmetic on SIV and symbols
    {
      DistanceVector distance_vector{loops.size()};
      // Independent but not yet supported.
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(84), store[2], &distance_vector));
    }

    // 101 -> 102 tests looking through symbol arithmetic on SIV and symbols
    {
      DistanceVector distance_vector{loops.size()};
      // Independent.
      EXPECT_TRUE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(101), store[3], &distance_vector));
    }
  }
  // For the loop in function b.
  {
    const Function* f = spvtest::GetFunction(module, 8);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[4];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 114)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 4; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // independent due to loop bounds (won't enter when N <= 0).
    // 129 -> 130 tests looking through SIV and symbols with multiplication.
    {
      DistanceVector distance_vector{loops.size()};
      // Independent but not yet supported.
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(129), store[0], &distance_vector));
    }

    // 140 -> 143 tests looking through SIV and symbols with multiplication and
    // + C.
    {
      DistanceVector distance_vector{loops.size()};
      // Independent.
      EXPECT_TRUE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(140), store[1], &distance_vector));
    }

    // 157 -> 158 tests looking through arithmetic on SIV and symbols.
    {
      DistanceVector distance_vector{loops.size()};
      // Independent but not yet supported.
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(157), store[2], &distance_vector));
    }

    // 174 -> 175 tests looking through symbol arithmetic on SIV and symbols.
    {
      DistanceVector distance_vector{loops.size()};
      // Independent.
      EXPECT_TRUE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(174), store[3], &distance_vector));
    }
  }
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
void a() {
  int[6] arr;
  int N = 5;
  for (int i = 1; i < N; i++) {
    arr[i] = arr[N-i];
  }
}
void b() {
  int[6] arr;
  int N = 5;
  for (int i = 1; i < N; i++) {
    arr[N-i] = arr[i];
  }
}
void c() {
  int[11] arr;
  int N = 10;
  for (int i = 1; i < N; i++) {
    arr[i] = arr[N-i+1];
  }
}
void d() {
  int[11] arr;
  int N = 10;
  for (int i = 1; i < N; i++) {
    arr[N-i+1] = arr[i];
  }
}
void e() {
  int[6] arr;
  int N = 5;
  for (int i = N; i > 0; i--) {
    arr[i] = arr[N-i];
  }
}
void f() {
  int[6] arr;
  int N = 5;
  for (int i = N; i > 0; i--) {
    arr[N-i] = arr[i];
  }
}
void g() {
  int[11] arr;
  int N = 10;
  for (int i = N; i > 0; i--) {
    arr[i] = arr[N-i+1];
  }
}
void h() {
  int[11] arr;
  int N = 10;
  for (int i = N; i > 0; i--) {
    arr[N-i+1] = arr[i];
  }
}
void main(){
  a();
  b();
  c();
  d();
  e();
  f();
  g();
  h();
}
*/
TEST(DependencyAnalysis, Crossing) {
  const std::string text = R"(               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %6 "a("
               OpName %8 "b("
               OpName %10 "c("
               OpName %12 "d("
               OpName %14 "e("
               OpName %16 "f("
               OpName %18 "g("
               OpName %20 "h("
               OpName %24 "N"
               OpName %26 "i"
               OpName %41 "arr"
               OpName %51 "N"
               OpName %52 "i"
               OpName %61 "arr"
               OpName %71 "N"
               OpName %73 "i"
               OpName %85 "arr"
               OpName %96 "N"
               OpName %97 "i"
               OpName %106 "arr"
               OpName %117 "N"
               OpName %118 "i"
               OpName %128 "arr"
               OpName %138 "N"
               OpName %139 "i"
               OpName %148 "arr"
               OpName %158 "N"
               OpName %159 "i"
               OpName %168 "arr"
               OpName %179 "N"
               OpName %180 "i"
               OpName %189 "arr"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %22 = OpTypeInt 32 1
         %23 = OpTypePointer Function %22
         %25 = OpConstant %22 5
         %27 = OpConstant %22 1
         %35 = OpTypeBool
         %37 = OpTypeInt 32 0
         %38 = OpConstant %37 6
         %39 = OpTypeArray %22 %38
         %40 = OpTypePointer Function %39
         %72 = OpConstant %22 10
         %82 = OpConstant %37 11
         %83 = OpTypeArray %22 %82
         %84 = OpTypePointer Function %83
        %126 = OpConstant %22 0
          %4 = OpFunction %2 None %3
          %5 = OpLabel
        %200 = OpFunctionCall %2 %6
        %201 = OpFunctionCall %2 %8
        %202 = OpFunctionCall %2 %10
        %203 = OpFunctionCall %2 %12
        %204 = OpFunctionCall %2 %14
        %205 = OpFunctionCall %2 %16
        %206 = OpFunctionCall %2 %18
        %207 = OpFunctionCall %2 %20
               OpReturn
               OpFunctionEnd
          %6 = OpFunction %2 None %3
          %7 = OpLabel
         %24 = OpVariable %23 Function
         %26 = OpVariable %23 Function
         %41 = OpVariable %40 Function
               OpStore %24 %25
               OpStore %26 %27
               OpBranch %28
         %28 = OpLabel
        %208 = OpPhi %22 %27 %7 %50 %31
               OpLoopMerge %30 %31 None
               OpBranch %32
         %32 = OpLabel
         %36 = OpSLessThan %35 %208 %25
               OpBranchConditional %36 %29 %30
         %29 = OpLabel
         %45 = OpISub %22 %25 %208
         %46 = OpAccessChain %23 %41 %45
         %47 = OpLoad %22 %46
         %48 = OpAccessChain %23 %41 %208
               OpStore %48 %47
               OpBranch %31
         %31 = OpLabel
         %50 = OpIAdd %22 %208 %27
               OpStore %26 %50
               OpBranch %28
         %30 = OpLabel
               OpReturn
               OpFunctionEnd
          %8 = OpFunction %2 None %3
          %9 = OpLabel
         %51 = OpVariable %23 Function
         %52 = OpVariable %23 Function
         %61 = OpVariable %40 Function
               OpStore %51 %25
               OpStore %52 %27
               OpBranch %53
         %53 = OpLabel
        %209 = OpPhi %22 %27 %9 %70 %56
               OpLoopMerge %55 %56 None
               OpBranch %57
         %57 = OpLabel
         %60 = OpSLessThan %35 %209 %25
               OpBranchConditional %60 %54 %55
         %54 = OpLabel
         %64 = OpISub %22 %25 %209
         %66 = OpAccessChain %23 %61 %209
         %67 = OpLoad %22 %66
         %68 = OpAccessChain %23 %61 %64
               OpStore %68 %67
               OpBranch %56
         %56 = OpLabel
         %70 = OpIAdd %22 %209 %27
               OpStore %52 %70
               OpBranch %53
         %55 = OpLabel
               OpReturn
               OpFunctionEnd
         %10 = OpFunction %2 None %3
         %11 = OpLabel
         %71 = OpVariable %23 Function
         %73 = OpVariable %23 Function
         %85 = OpVariable %84 Function
               OpStore %71 %72
               OpStore %73 %27
               OpBranch %74
         %74 = OpLabel
        %210 = OpPhi %22 %27 %11 %95 %77
               OpLoopMerge %76 %77 None
               OpBranch %78
         %78 = OpLabel
         %81 = OpSLessThan %35 %210 %72
               OpBranchConditional %81 %75 %76
         %75 = OpLabel
         %89 = OpISub %22 %72 %210
         %90 = OpIAdd %22 %89 %27
         %91 = OpAccessChain %23 %85 %90
         %92 = OpLoad %22 %91
         %93 = OpAccessChain %23 %85 %210
               OpStore %93 %92
               OpBranch %77
         %77 = OpLabel
         %95 = OpIAdd %22 %210 %27
               OpStore %73 %95
               OpBranch %74
         %76 = OpLabel
               OpReturn
               OpFunctionEnd
         %12 = OpFunction %2 None %3
         %13 = OpLabel
         %96 = OpVariable %23 Function
         %97 = OpVariable %23 Function
        %106 = OpVariable %84 Function
               OpStore %96 %72
               OpStore %97 %27
               OpBranch %98
         %98 = OpLabel
        %211 = OpPhi %22 %27 %13 %116 %101
               OpLoopMerge %100 %101 None
               OpBranch %102
        %102 = OpLabel
        %105 = OpSLessThan %35 %211 %72
               OpBranchConditional %105 %99 %100
         %99 = OpLabel
        %109 = OpISub %22 %72 %211
        %110 = OpIAdd %22 %109 %27
        %112 = OpAccessChain %23 %106 %211
        %113 = OpLoad %22 %112
        %114 = OpAccessChain %23 %106 %110
               OpStore %114 %113
               OpBranch %101
        %101 = OpLabel
        %116 = OpIAdd %22 %211 %27
               OpStore %97 %116
               OpBranch %98
        %100 = OpLabel
               OpReturn
               OpFunctionEnd
         %14 = OpFunction %2 None %3
         %15 = OpLabel
        %117 = OpVariable %23 Function
        %118 = OpVariable %23 Function
        %128 = OpVariable %40 Function
               OpStore %117 %25
               OpStore %118 %25
               OpBranch %120
        %120 = OpLabel
        %212 = OpPhi %22 %25 %15 %137 %123
               OpLoopMerge %122 %123 None
               OpBranch %124
        %124 = OpLabel
        %127 = OpSGreaterThan %35 %212 %126
               OpBranchConditional %127 %121 %122
        %121 = OpLabel
        %132 = OpISub %22 %25 %212
        %133 = OpAccessChain %23 %128 %132
        %134 = OpLoad %22 %133
        %135 = OpAccessChain %23 %128 %212
               OpStore %135 %134
               OpBranch %123
        %123 = OpLabel
        %137 = OpISub %22 %212 %27
               OpStore %118 %137
               OpBranch %120
        %122 = OpLabel
               OpReturn
               OpFunctionEnd
         %16 = OpFunction %2 None %3
         %17 = OpLabel
        %138 = OpVariable %23 Function
        %139 = OpVariable %23 Function
        %148 = OpVariable %40 Function
               OpStore %138 %25
               OpStore %139 %25
               OpBranch %141
        %141 = OpLabel
        %213 = OpPhi %22 %25 %17 %157 %144
               OpLoopMerge %143 %144 None
               OpBranch %145
        %145 = OpLabel
        %147 = OpSGreaterThan %35 %213 %126
               OpBranchConditional %147 %142 %143
        %142 = OpLabel
        %151 = OpISub %22 %25 %213
        %153 = OpAccessChain %23 %148 %213
        %154 = OpLoad %22 %153
        %155 = OpAccessChain %23 %148 %151
               OpStore %155 %154
               OpBranch %144
        %144 = OpLabel
        %157 = OpISub %22 %213 %27
               OpStore %139 %157
               OpBranch %141
        %143 = OpLabel
               OpReturn
               OpFunctionEnd
         %18 = OpFunction %2 None %3
         %19 = OpLabel
        %158 = OpVariable %23 Function
        %159 = OpVariable %23 Function
        %168 = OpVariable %84 Function
               OpStore %158 %72
               OpStore %159 %72
               OpBranch %161
        %161 = OpLabel
        %214 = OpPhi %22 %72 %19 %178 %164
               OpLoopMerge %163 %164 None
               OpBranch %165
        %165 = OpLabel
        %167 = OpSGreaterThan %35 %214 %126
               OpBranchConditional %167 %162 %163
        %162 = OpLabel
        %172 = OpISub %22 %72 %214
        %173 = OpIAdd %22 %172 %27
        %174 = OpAccessChain %23 %168 %173
        %175 = OpLoad %22 %174
        %176 = OpAccessChain %23 %168 %214
               OpStore %176 %175
               OpBranch %164
        %164 = OpLabel
        %178 = OpISub %22 %214 %27
               OpStore %159 %178
               OpBranch %161
        %163 = OpLabel
               OpReturn
               OpFunctionEnd
         %20 = OpFunction %2 None %3
         %21 = OpLabel
        %179 = OpVariable %23 Function
        %180 = OpVariable %23 Function
        %189 = OpVariable %84 Function
               OpStore %179 %72
               OpStore %180 %72
               OpBranch %182
        %182 = OpLabel
        %215 = OpPhi %22 %72 %21 %199 %185
               OpLoopMerge %184 %185 None
               OpBranch %186
        %186 = OpLabel
        %188 = OpSGreaterThan %35 %215 %126
               OpBranchConditional %188 %183 %184
        %183 = OpLabel
        %192 = OpISub %22 %72 %215
        %193 = OpIAdd %22 %192 %27
        %195 = OpAccessChain %23 %189 %215
        %196 = OpLoad %22 %195
        %197 = OpAccessChain %23 %189 %193
               OpStore %197 %196
               OpBranch %185
        %185 = OpLabel
        %199 = OpISub %22 %215 %27
               OpStore %180 %199
               OpBranch %182
        %184 = OpLabel
               OpReturn
               OpFunctionEnd
)";
  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;

  // First two tests can be split into two loops.
  // Tests even crossing subscripts from low to high indexes.
  // 47 -> 48
  {
    const Function* f = spvtest::GetFunction(module, 6);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store = nullptr;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 29)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }
    }
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(47),
                                        store, &distance_vector));
  }

  // Tests even crossing subscripts from high to low indexes.
  // 67 -> 68
  {
    const Function* f = spvtest::GetFunction(module, 8);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store = nullptr;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 54)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }
    }
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(67),
                                        store, &distance_vector));
  }

  // Next two tests can have an end peeled, then be split.
  // Tests uneven crossing subscripts from low to high indexes.
  // 92 -> 93
  {
    const Function* f = spvtest::GetFunction(module, 10);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store = nullptr;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 75)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }
    }
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(92),
                                        store, &distance_vector));
  }

  // Tests uneven crossing subscripts from high to low indexes.
  // 113 -> 114
  {
    const Function* f = spvtest::GetFunction(module, 12);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store = nullptr;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 99)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }
    }
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(113),
                                        store, &distance_vector));
  }

  // First two tests can be split into two loops.
  // Tests even crossing subscripts from low to high indexes.
  // 134 -> 135
  {
    const Function* f = spvtest::GetFunction(module, 14);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store = nullptr;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 121)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }
    }
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(134),
                                        store, &distance_vector));
  }

  // Tests even crossing subscripts from high to low indexes.
  // 154 -> 155
  {
    const Function* f = spvtest::GetFunction(module, 16);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store = nullptr;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 142)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }
    }
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(154),
                                        store, &distance_vector));
  }

  // Next two tests can have an end peeled, then be split.
  // Tests uneven crossing subscripts from low to high indexes.
  // 175 -> 176
  {
    const Function* f = spvtest::GetFunction(module, 18);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store = nullptr;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 162)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }
    }
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(175),
                                        store, &distance_vector));
  }

  // Tests uneven crossing subscripts from high to low indexes.
  // 196 -> 197
  {
    const Function* f = spvtest::GetFunction(module, 20);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store = nullptr;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 183)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }
    }
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(196),
                                        store, &distance_vector));
  }
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
void a() {
  int[10] arr;
  for (int i = 0; i < 10; i++) {
    arr[0] = arr[i]; // peel first
    arr[i] = arr[0]; // peel first
    arr[9] = arr[i]; // peel last
    arr[i] = arr[9]; // peel last
  }
}
void b() {
  int[11] arr;
  for (int i = 0; i <= 10; i++) {
    arr[0] = arr[i]; // peel first
    arr[i] = arr[0]; // peel first
    arr[10] = arr[i]; // peel last
    arr[i] = arr[10]; // peel last

  }
}
void c() {
  int[11] arr;
  for (int i = 10; i > 0; i--) {
    arr[10] = arr[i]; // peel first
    arr[i] = arr[10]; // peel first
    arr[1] = arr[i]; // peel last
    arr[i] = arr[1]; // peel last

  }
}
void d() {
  int[11] arr;
  for (int i = 10; i >= 0; i--) {
    arr[10] = arr[i]; // peel first
    arr[i] = arr[10]; // peel first
    arr[0] = arr[i]; // peel last
    arr[i] = arr[0]; // peel last

  }
}
void main(){
  a();
  b();
  c();
  d();
}
*/
TEST(DependencyAnalysis, WeakZeroSIV) {
  const std::string text = R"(               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %6 "a("
               OpName %8 "b("
               OpName %10 "c("
               OpName %12 "d("
               OpName %16 "i"
               OpName %31 "arr"
               OpName %52 "i"
               OpName %63 "arr"
               OpName %82 "i"
               OpName %90 "arr"
               OpName %109 "i"
               OpName %117 "arr"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %14 = OpTypeInt 32 1
         %15 = OpTypePointer Function %14
         %17 = OpConstant %14 0
         %24 = OpConstant %14 10
         %25 = OpTypeBool
         %27 = OpTypeInt 32 0
         %28 = OpConstant %27 10
         %29 = OpTypeArray %14 %28
         %30 = OpTypePointer Function %29
         %40 = OpConstant %14 9
         %50 = OpConstant %14 1
         %60 = OpConstant %27 11
         %61 = OpTypeArray %14 %60
         %62 = OpTypePointer Function %61
          %4 = OpFunction %2 None %3
          %5 = OpLabel
        %136 = OpFunctionCall %2 %6
        %137 = OpFunctionCall %2 %8
        %138 = OpFunctionCall %2 %10
        %139 = OpFunctionCall %2 %12
               OpReturn
               OpFunctionEnd
          %6 = OpFunction %2 None %3
          %7 = OpLabel
         %16 = OpVariable %15 Function
         %31 = OpVariable %30 Function
               OpStore %16 %17
               OpBranch %18
         %18 = OpLabel
        %140 = OpPhi %14 %17 %7 %51 %21
               OpLoopMerge %20 %21 None
               OpBranch %22
         %22 = OpLabel
         %26 = OpSLessThan %25 %140 %24
               OpBranchConditional %26 %19 %20
         %19 = OpLabel
         %33 = OpAccessChain %15 %31 %140
         %34 = OpLoad %14 %33
         %35 = OpAccessChain %15 %31 %17
               OpStore %35 %34
         %37 = OpAccessChain %15 %31 %17
         %38 = OpLoad %14 %37
         %39 = OpAccessChain %15 %31 %140
               OpStore %39 %38
         %42 = OpAccessChain %15 %31 %140
         %43 = OpLoad %14 %42
         %44 = OpAccessChain %15 %31 %40
               OpStore %44 %43
         %46 = OpAccessChain %15 %31 %40
         %47 = OpLoad %14 %46
         %48 = OpAccessChain %15 %31 %140
               OpStore %48 %47
               OpBranch %21
         %21 = OpLabel
         %51 = OpIAdd %14 %140 %50
               OpStore %16 %51
               OpBranch %18
         %20 = OpLabel
               OpReturn
               OpFunctionEnd
          %8 = OpFunction %2 None %3
          %9 = OpLabel
         %52 = OpVariable %15 Function
         %63 = OpVariable %62 Function
               OpStore %52 %17
               OpBranch %53
         %53 = OpLabel
        %141 = OpPhi %14 %17 %9 %81 %56
               OpLoopMerge %55 %56 None
               OpBranch %57
         %57 = OpLabel
         %59 = OpSLessThanEqual %25 %141 %24
               OpBranchConditional %59 %54 %55
         %54 = OpLabel
         %65 = OpAccessChain %15 %63 %141
         %66 = OpLoad %14 %65
         %67 = OpAccessChain %15 %63 %17
               OpStore %67 %66
         %69 = OpAccessChain %15 %63 %17
         %70 = OpLoad %14 %69
         %71 = OpAccessChain %15 %63 %141
               OpStore %71 %70
         %73 = OpAccessChain %15 %63 %141
         %74 = OpLoad %14 %73
         %75 = OpAccessChain %15 %63 %24
               OpStore %75 %74
         %77 = OpAccessChain %15 %63 %24
         %78 = OpLoad %14 %77
         %79 = OpAccessChain %15 %63 %141
               OpStore %79 %78
               OpBranch %56
         %56 = OpLabel
         %81 = OpIAdd %14 %141 %50
               OpStore %52 %81
               OpBranch %53
         %55 = OpLabel
               OpReturn
               OpFunctionEnd
         %10 = OpFunction %2 None %3
         %11 = OpLabel
         %82 = OpVariable %15 Function
         %90 = OpVariable %62 Function
               OpStore %82 %24
               OpBranch %83
         %83 = OpLabel
        %142 = OpPhi %14 %24 %11 %108 %86
               OpLoopMerge %85 %86 None
               OpBranch %87
         %87 = OpLabel
         %89 = OpSGreaterThan %25 %142 %17
               OpBranchConditional %89 %84 %85
         %84 = OpLabel
         %92 = OpAccessChain %15 %90 %142
         %93 = OpLoad %14 %92
         %94 = OpAccessChain %15 %90 %24
               OpStore %94 %93
         %96 = OpAccessChain %15 %90 %24
         %97 = OpLoad %14 %96
         %98 = OpAccessChain %15 %90 %142
               OpStore %98 %97
        %100 = OpAccessChain %15 %90 %142
        %101 = OpLoad %14 %100
        %102 = OpAccessChain %15 %90 %50
               OpStore %102 %101
        %104 = OpAccessChain %15 %90 %50
        %105 = OpLoad %14 %104
        %106 = OpAccessChain %15 %90 %142
               OpStore %106 %105
               OpBranch %86
         %86 = OpLabel
        %108 = OpISub %14 %142 %50
               OpStore %82 %108
               OpBranch %83
         %85 = OpLabel
               OpReturn
               OpFunctionEnd
         %12 = OpFunction %2 None %3
         %13 = OpLabel
        %109 = OpVariable %15 Function
        %117 = OpVariable %62 Function
               OpStore %109 %24
               OpBranch %110
        %110 = OpLabel
        %143 = OpPhi %14 %24 %13 %135 %113
               OpLoopMerge %112 %113 None
               OpBranch %114
        %114 = OpLabel
        %116 = OpSGreaterThanEqual %25 %143 %17
               OpBranchConditional %116 %111 %112
        %111 = OpLabel
        %119 = OpAccessChain %15 %117 %143
        %120 = OpLoad %14 %119
        %121 = OpAccessChain %15 %117 %24
               OpStore %121 %120
        %123 = OpAccessChain %15 %117 %24
        %124 = OpLoad %14 %123
        %125 = OpAccessChain %15 %117 %143
               OpStore %125 %124
        %127 = OpAccessChain %15 %117 %143
        %128 = OpLoad %14 %127
        %129 = OpAccessChain %15 %117 %17
               OpStore %129 %128
        %131 = OpAccessChain %15 %117 %17
        %132 = OpLoad %14 %131
        %133 = OpAccessChain %15 %117 %143
               OpStore %133 %132
               OpBranch %113
        %113 = OpLabel
        %135 = OpISub %14 %143 %50
               OpStore %109 %135
               OpBranch %110
        %112 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  // For the loop in function a
  {
    const Function* f = spvtest::GetFunction(module, 6);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[4];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 19)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 4; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // Tests identifying peel first with weak zero with destination as zero
    // index.
    // 34 -> 35
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(34), store[0], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
    }

    // Tests identifying peel first with weak zero with source as zero index.
    // 38 -> 39
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(38), store[1], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
    }

    // Tests identifying peel first with weak zero with destination as zero
    // index.
    // 43 -> 44
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(43), store[2], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
    }

    // Tests identifying peel first with weak zero with source as zero index.
    // 47 -> 48
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(47), store[3], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
    }
  }
  // For the loop in function b
  {
    const Function* f = spvtest::GetFunction(module, 8);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[4];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 54)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 4; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // Tests identifying peel first with weak zero with destination as zero
    // index.
    // 66 -> 67
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(66), store[0], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
    }

    // Tests identifying peel first with weak zero with source as zero index.
    // 70 -> 71
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(70), store[1], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
    }

    // Tests identifying peel first with weak zero with destination as zero
    // index.
    // 74 -> 75
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(74), store[2], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
    }

    // Tests identifying peel first with weak zero with source as zero index.
    // 78 -> 79
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(78), store[3], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
    }
  }
  // For the loop in function c
  {
    const Function* f = spvtest::GetFunction(module, 10);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};
    const Instruction* store[4];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 84)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 4; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // Tests identifying peel first with weak zero with destination as zero
    // index.
    // 93 -> 94
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(93), store[0], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
    }

    // Tests identifying peel first with weak zero with source as zero index.
    // 97 -> 98
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(97), store[1], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
    }

    // Tests identifying peel first with weak zero with destination as zero
    // index.
    // 101 -> 102
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(101), store[2], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
    }

    // Tests identifying peel first with weak zero with source as zero index.
    // 105 -> 106
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(105), store[3], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
    }
  }
  // For the loop in function d
  {
    const Function* f = spvtest::GetFunction(module, 12);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    Loop* loop = &ld.GetLoopByIndex(0);
    std::vector<const Loop*> loops{loop};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[4];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 111)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 4; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // Tests identifying peel first with weak zero with destination as zero
    // index.
    // 120 -> 121
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(120), store[0], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
    }

    // Tests identifying peel first with weak zero with source as zero index.
    // 124 -> 125
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(124), store[1], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_first);
    }

    // Tests identifying peel first with weak zero with destination as zero
    // index.
    // 128 -> 129
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(128), store[2], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
    }

    // Tests identifying peel first with weak zero with source as zero index.
    // 132 -> 133
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(132), store[3], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::PEEL);
      EXPECT_TRUE(distance_vector.GetEntries()[0].peel_last);
    }
  }
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
void main(){
  int[10][10] arr;
  for (int i = 0; i < 10; i++) {
    arr[i][i] = arr[i][i];
    arr[0][i] = arr[1][i];
    arr[1][i] = arr[0][i];
    arr[i][0] = arr[i][1];
    arr[i][1] = arr[i][0];
    arr[0][1] = arr[1][0];
  }
}
*/
TEST(DependencyAnalysis, MultipleSubscriptZIVSIV) {
  const std::string text = R"(               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %8 "i"
               OpName %24 "arr"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 0
         %16 = OpConstant %6 10
         %17 = OpTypeBool
         %19 = OpTypeInt 32 0
         %20 = OpConstant %19 10
         %21 = OpTypeArray %6 %20
         %22 = OpTypeArray %21 %20
         %23 = OpTypePointer Function %22
         %33 = OpConstant %6 1
          %4 = OpFunction %2 None %3
          %5 = OpLabel
          %8 = OpVariable %7 Function
         %24 = OpVariable %23 Function
               OpStore %8 %9
               OpBranch %10
         %10 = OpLabel
         %58 = OpPhi %6 %9 %5 %57 %13
               OpLoopMerge %12 %13 None
               OpBranch %14
         %14 = OpLabel
         %18 = OpSLessThan %17 %58 %16
               OpBranchConditional %18 %11 %12
         %11 = OpLabel
         %29 = OpAccessChain %7 %24 %58 %58
         %30 = OpLoad %6 %29
         %31 = OpAccessChain %7 %24 %58 %58
               OpStore %31 %30
         %35 = OpAccessChain %7 %24 %33 %58
         %36 = OpLoad %6 %35
         %37 = OpAccessChain %7 %24 %9 %58
               OpStore %37 %36
         %40 = OpAccessChain %7 %24 %9 %58
         %41 = OpLoad %6 %40
         %42 = OpAccessChain %7 %24 %33 %58
               OpStore %42 %41
         %45 = OpAccessChain %7 %24 %58 %33
         %46 = OpLoad %6 %45
         %47 = OpAccessChain %7 %24 %58 %9
               OpStore %47 %46
         %50 = OpAccessChain %7 %24 %58 %9
         %51 = OpLoad %6 %50
         %52 = OpAccessChain %7 %24 %58 %33
               OpStore %52 %51
         %53 = OpAccessChain %7 %24 %33 %9
         %54 = OpLoad %6 %53
         %55 = OpAccessChain %7 %24 %9 %33
               OpStore %55 %54
               OpBranch %13
         %13 = OpLabel
         %57 = OpIAdd %6 %58 %33
               OpStore %8 %57
               OpBranch %10
         %12 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  const Function* f = spvtest::GetFunction(module, 4);
  LoopDescriptor& ld = *context->GetLoopDescriptor(f);

  Loop* loop = &ld.GetLoopByIndex(0);
  std::vector<const Loop*> loops{loop};
  LoopDependenceAnalysis analysis{context.get(), loops};

  const Instruction* store[6];
  int stores_found = 0;
  for (const Instruction& inst : *spvtest::GetBasicBlock(f, 11)) {
    if (inst.opcode() == spv::Op::OpStore) {
      store[stores_found] = &inst;
      ++stores_found;
    }
  }

  for (int i = 0; i < 6; ++i) {
    EXPECT_TRUE(store[i]);
  }

  // 30 -> 31
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_FALSE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(30),
                                        store[0], &distance_vector));
    EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
              DistanceEntry::DependenceInformation::DISTANCE);
    EXPECT_EQ(distance_vector.GetEntries()[0].direction,
              DistanceEntry::Directions::EQ);
    EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
  }

  // 36 -> 37
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(36),
                                       store[1], &distance_vector));
  }

  // 41 -> 42
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(41),
                                       store[2], &distance_vector));
  }

  // 46 -> 47
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(46),
                                       store[3], &distance_vector));
    EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
              DistanceEntry::DependenceInformation::DISTANCE);
    EXPECT_EQ(distance_vector.GetEntries()[0].direction,
              DistanceEntry::Directions::EQ);
    EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
  }

  // 51 -> 52
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(51),
                                       store[4], &distance_vector));
    EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
              DistanceEntry::DependenceInformation::DISTANCE);
    EXPECT_EQ(distance_vector.GetEntries()[0].direction,
              DistanceEntry::Directions::EQ);
    EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
  }

  // 54 -> 55
  {
    DistanceVector distance_vector{loops.size()};
    EXPECT_TRUE(analysis.GetDependence(context->get_def_use_mgr()->GetDef(54),
                                       store[5], &distance_vector));
  }
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
void a(){
  int[10] arr;
  for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
      arr[j] = arr[j];
    }
  }
}
void b(){
  int[10] arr;
  for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
      arr[i] = arr[i];
    }
  }
}
void main() {
  a();
  b();
}
*/
TEST(DependencyAnalysis, IrrelevantSubscripts) {
  const std::string text = R"(               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %6 "a("
               OpName %8 "b("
               OpName %12 "i"
               OpName %23 "j"
               OpName %35 "arr"
               OpName %46 "i"
               OpName %54 "j"
               OpName %62 "arr"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %10 = OpTypeInt 32 1
         %11 = OpTypePointer Function %10
         %13 = OpConstant %10 0
         %20 = OpConstant %10 10
         %21 = OpTypeBool
         %31 = OpTypeInt 32 0
         %32 = OpConstant %31 10
         %33 = OpTypeArray %10 %32
         %34 = OpTypePointer Function %33
         %42 = OpConstant %10 1
          %4 = OpFunction %2 None %3
          %5 = OpLabel
         %72 = OpFunctionCall %2 %6
         %73 = OpFunctionCall %2 %8
               OpReturn
               OpFunctionEnd
          %6 = OpFunction %2 None %3
          %7 = OpLabel
         %12 = OpVariable %11 Function
         %23 = OpVariable %11 Function
         %35 = OpVariable %34 Function
               OpStore %12 %13
               OpBranch %14
         %14 = OpLabel
         %74 = OpPhi %10 %13 %7 %45 %17
               OpLoopMerge %16 %17 None
               OpBranch %18
         %18 = OpLabel
         %22 = OpSLessThan %21 %74 %20
               OpBranchConditional %22 %15 %16
         %15 = OpLabel
               OpStore %23 %13
               OpBranch %24
         %24 = OpLabel
         %75 = OpPhi %10 %13 %15 %43 %27
               OpLoopMerge %26 %27 None
               OpBranch %28
         %28 = OpLabel
         %30 = OpSLessThan %21 %75 %20
               OpBranchConditional %30 %25 %26
         %25 = OpLabel
         %38 = OpAccessChain %11 %35 %75
         %39 = OpLoad %10 %38
         %40 = OpAccessChain %11 %35 %75
               OpStore %40 %39
               OpBranch %27
         %27 = OpLabel
         %43 = OpIAdd %10 %75 %42
               OpStore %23 %43
               OpBranch %24
         %26 = OpLabel
               OpBranch %17
         %17 = OpLabel
         %45 = OpIAdd %10 %74 %42
               OpStore %12 %45
               OpBranch %14
         %16 = OpLabel
               OpReturn
               OpFunctionEnd
          %8 = OpFunction %2 None %3
          %9 = OpLabel
         %46 = OpVariable %11 Function
         %54 = OpVariable %11 Function
         %62 = OpVariable %34 Function
               OpStore %46 %13
               OpBranch %47
         %47 = OpLabel
         %77 = OpPhi %10 %13 %9 %71 %50
               OpLoopMerge %49 %50 None
               OpBranch %51
         %51 = OpLabel
         %53 = OpSLessThan %21 %77 %20
               OpBranchConditional %53 %48 %49
         %48 = OpLabel
               OpStore %54 %13
               OpBranch %55
         %55 = OpLabel
         %78 = OpPhi %10 %13 %48 %69 %58
               OpLoopMerge %57 %58 None
               OpBranch %59
         %59 = OpLabel
         %61 = OpSLessThan %21 %78 %20
               OpBranchConditional %61 %56 %57
         %56 = OpLabel
         %65 = OpAccessChain %11 %62 %77
         %66 = OpLoad %10 %65
         %67 = OpAccessChain %11 %62 %77
               OpStore %67 %66
               OpBranch %58
         %58 = OpLabel
         %69 = OpIAdd %10 %78 %42
               OpStore %54 %69
               OpBranch %55
         %57 = OpLabel
               OpBranch %50
         %50 = OpLabel
         %71 = OpIAdd %10 %77 %42
               OpStore %46 %71
               OpBranch %47
         %49 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  // For the loop in function a
  {
    const Function* f = spvtest::GetFunction(module, 6);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    std::vector<const Loop*> loops{&ld.GetLoopByIndex(1),
                                   &ld.GetLoopByIndex(0)};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[1];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 25)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 1; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // 39 -> 40
    {
      DistanceVector distance_vector{loops.size()};
      analysis.SetDebugStream(std::cout);
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(39), store[0], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::IRRELEVANT);
      EXPECT_EQ(distance_vector.GetEntries()[1].dependence_information,
                DistanceEntry::DependenceInformation::DISTANCE);
      EXPECT_EQ(distance_vector.GetEntries()[1].distance, 0);
    }
  }

  // For the loop in function b
  {
    const Function* f = spvtest::GetFunction(module, 8);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    std::vector<const Loop*> loops{&ld.GetLoopByIndex(1),
                                   &ld.GetLoopByIndex(0)};
    LoopDependenceAnalysis analysis{context.get(), loops};

    const Instruction* store[1];
    int stores_found = 0;
    for (const Instruction& inst : *spvtest::GetBasicBlock(f, 56)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store[stores_found] = &inst;
        ++stores_found;
      }
    }

    for (int i = 0; i < 1; ++i) {
      EXPECT_TRUE(store[i]);
    }

    // 66 -> 67
    {
      DistanceVector distance_vector{loops.size()};
      EXPECT_FALSE(analysis.GetDependence(
          context->get_def_use_mgr()->GetDef(66), store[0], &distance_vector));
      EXPECT_EQ(distance_vector.GetEntries()[0].dependence_information,
                DistanceEntry::DependenceInformation::DISTANCE);
      EXPECT_EQ(distance_vector.GetEntries()[0].distance, 0);
      EXPECT_EQ(distance_vector.GetEntries()[1].dependence_information,
                DistanceEntry::DependenceInformation::IRRELEVANT);
    }
  }
}

void CheckDependenceAndDirection(const Instruction* source,
                                 const Instruction* destination,
                                 bool expected_dependence,
                                 DistanceVector expected_distance,
                                 LoopDependenceAnalysis* analysis) {
  DistanceVector dv_entry(2);
  EXPECT_EQ(expected_dependence,
            analysis->GetDependence(source, destination, &dv_entry));
  EXPECT_EQ(expected_distance, dv_entry);
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
layout(location = 0) in vec4 c;
void main(){
  int[10] arr;
  int a = 2;
  int b = 3;
  int N = int(c.x);
  for (int i = 0; i < 10; i++) {
    for (int j = 2; j < 10; j++) {
      arr[i] = arr[j]; // 0
      arr[j] = arr[i]; // 1
      arr[j-2] = arr[i+3]; // 2
      arr[j-a] = arr[i+b]; // 3
      arr[2*i] = arr[4*j+3]; // 4, independent
      arr[2*i] = arr[4*j]; // 5
      arr[i+j] = arr[i+j]; // 6
      arr[10*i+j] = arr[10*i+j]; // 7
      arr[10*i+10*j] = arr[10*i+10*j+3]; // 8, independent
      arr[10*i+10*j] = arr[10*i+N*j+3]; // 9, bail out because of N coefficient
      arr[10*i+10*j] = arr[10*i+10*j+N]; // 10, bail out because of N constant
                                         // term
      arr[10*i+N*j] = arr[10*i+10*j+3]; // 11, bail out because of N coefficient
      arr[10*i+10*j+N] = arr[10*i+10*j]; // 12, bail out because of N constant
                                         // term
      arr[10*i] = arr[5*j]; // 13, independent
      arr[5*i] = arr[10*j]; // 14, independent
      arr[9*i] = arr[3*j]; // 15, independent
      arr[3*i] = arr[9*j]; // 16, independent
    }
  }
}
*/
TEST(DependencyAnalysis, MIV) {
  const std::string text = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main" %16
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %8 "a"
               OpName %10 "b"
               OpName %12 "N"
               OpName %16 "c"
               OpName %23 "i"
               OpName %34 "j"
               OpName %45 "arr"
               OpDecorate %16 Location 0
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 2
         %11 = OpConstant %6 3
         %13 = OpTypeFloat 32
         %14 = OpTypeVector %13 4
         %15 = OpTypePointer Input %14
         %16 = OpVariable %15 Input
         %17 = OpTypeInt 32 0
         %18 = OpConstant %17 0
         %19 = OpTypePointer Input %13
         %24 = OpConstant %6 0
         %31 = OpConstant %6 10
         %32 = OpTypeBool
         %42 = OpConstant %17 10
         %43 = OpTypeArray %6 %42
         %44 = OpTypePointer Function %43
         %74 = OpConstant %6 4
        %184 = OpConstant %6 5
        %197 = OpConstant %6 9
        %213 = OpConstant %6 1
        %218 = OpUndef %6
          %4 = OpFunction %2 None %3
          %5 = OpLabel
          %8 = OpVariable %7 Function
         %10 = OpVariable %7 Function
         %12 = OpVariable %7 Function
         %23 = OpVariable %7 Function
         %34 = OpVariable %7 Function
         %45 = OpVariable %44 Function
               OpStore %8 %9
               OpStore %10 %11
         %20 = OpAccessChain %19 %16 %18
         %21 = OpLoad %13 %20
         %22 = OpConvertFToS %6 %21
               OpStore %12 %22
               OpStore %23 %24
               OpBranch %25
         %25 = OpLabel
        %217 = OpPhi %6 %24 %5 %216 %28
        %219 = OpPhi %6 %218 %5 %220 %28
               OpLoopMerge %27 %28 None
               OpBranch %29
         %29 = OpLabel
         %33 = OpSLessThan %32 %217 %31
               OpBranchConditional %33 %26 %27
         %26 = OpLabel
               OpStore %34 %9
               OpBranch %35
         %35 = OpLabel
        %220 = OpPhi %6 %9 %26 %214 %38
               OpLoopMerge %37 %38 None
               OpBranch %39
         %39 = OpLabel
         %41 = OpSLessThan %32 %220 %31
               OpBranchConditional %41 %36 %37
         %36 = OpLabel
         %48 = OpAccessChain %7 %45 %220
         %49 = OpLoad %6 %48
         %50 = OpAccessChain %7 %45 %217
               OpStore %50 %49
         %53 = OpAccessChain %7 %45 %217
         %54 = OpLoad %6 %53
         %55 = OpAccessChain %7 %45 %220
               OpStore %55 %54
         %57 = OpISub %6 %220 %9
         %59 = OpIAdd %6 %217 %11
         %60 = OpAccessChain %7 %45 %59
         %61 = OpLoad %6 %60
         %62 = OpAccessChain %7 %45 %57
               OpStore %62 %61
         %65 = OpISub %6 %220 %9
         %68 = OpIAdd %6 %217 %11
         %69 = OpAccessChain %7 %45 %68
         %70 = OpLoad %6 %69
         %71 = OpAccessChain %7 %45 %65
               OpStore %71 %70
         %73 = OpIMul %6 %9 %217
         %76 = OpIMul %6 %74 %220
         %77 = OpIAdd %6 %76 %11
         %78 = OpAccessChain %7 %45 %77
         %79 = OpLoad %6 %78
         %80 = OpAccessChain %7 %45 %73
               OpStore %80 %79
         %82 = OpIMul %6 %9 %217
         %84 = OpIMul %6 %74 %220
         %85 = OpAccessChain %7 %45 %84
         %86 = OpLoad %6 %85
         %87 = OpAccessChain %7 %45 %82
               OpStore %87 %86
         %90 = OpIAdd %6 %217 %220
         %93 = OpIAdd %6 %217 %220
         %94 = OpAccessChain %7 %45 %93
         %95 = OpLoad %6 %94
         %96 = OpAccessChain %7 %45 %90
               OpStore %96 %95
         %98 = OpIMul %6 %31 %217
        %100 = OpIAdd %6 %98 %220
        %102 = OpIMul %6 %31 %217
        %104 = OpIAdd %6 %102 %220
        %105 = OpAccessChain %7 %45 %104
        %106 = OpLoad %6 %105
        %107 = OpAccessChain %7 %45 %100
               OpStore %107 %106
        %109 = OpIMul %6 %31 %217
        %111 = OpIMul %6 %31 %220
        %112 = OpIAdd %6 %109 %111
        %114 = OpIMul %6 %31 %217
        %116 = OpIMul %6 %31 %220
        %117 = OpIAdd %6 %114 %116
        %118 = OpIAdd %6 %117 %11
        %119 = OpAccessChain %7 %45 %118
        %120 = OpLoad %6 %119
        %121 = OpAccessChain %7 %45 %112
               OpStore %121 %120
        %123 = OpIMul %6 %31 %217
        %125 = OpIMul %6 %31 %220
        %126 = OpIAdd %6 %123 %125
        %128 = OpIMul %6 %31 %217
        %131 = OpIMul %6 %22 %220
        %132 = OpIAdd %6 %128 %131
        %133 = OpIAdd %6 %132 %11
        %134 = OpAccessChain %7 %45 %133
        %135 = OpLoad %6 %134
        %136 = OpAccessChain %7 %45 %126
               OpStore %136 %135
        %138 = OpIMul %6 %31 %217
        %140 = OpIMul %6 %31 %220
        %141 = OpIAdd %6 %138 %140
        %143 = OpIMul %6 %31 %217
        %145 = OpIMul %6 %31 %220
        %146 = OpIAdd %6 %143 %145
        %148 = OpIAdd %6 %146 %22
        %149 = OpAccessChain %7 %45 %148
        %150 = OpLoad %6 %149
        %151 = OpAccessChain %7 %45 %141
               OpStore %151 %150
        %153 = OpIMul %6 %31 %217
        %156 = OpIMul %6 %22 %220
        %157 = OpIAdd %6 %153 %156
        %159 = OpIMul %6 %31 %217
        %161 = OpIMul %6 %31 %220
        %162 = OpIAdd %6 %159 %161
        %163 = OpIAdd %6 %162 %11
        %164 = OpAccessChain %7 %45 %163
        %165 = OpLoad %6 %164
        %166 = OpAccessChain %7 %45 %157
               OpStore %166 %165
        %168 = OpIMul %6 %31 %217
        %170 = OpIMul %6 %31 %220
        %171 = OpIAdd %6 %168 %170
        %173 = OpIAdd %6 %171 %22
        %175 = OpIMul %6 %31 %217
        %177 = OpIMul %6 %31 %220
        %178 = OpIAdd %6 %175 %177
        %179 = OpAccessChain %7 %45 %178
        %180 = OpLoad %6 %179
        %181 = OpAccessChain %7 %45 %173
               OpStore %181 %180
        %183 = OpIMul %6 %31 %217
        %186 = OpIMul %6 %184 %220
        %187 = OpAccessChain %7 %45 %186
        %188 = OpLoad %6 %187
        %189 = OpAccessChain %7 %45 %183
               OpStore %189 %188
        %191 = OpIMul %6 %184 %217
        %193 = OpIMul %6 %31 %220
        %194 = OpAccessChain %7 %45 %193
        %195 = OpLoad %6 %194
        %196 = OpAccessChain %7 %45 %191
               OpStore %196 %195
        %199 = OpIMul %6 %197 %217
        %201 = OpIMul %6 %11 %220
        %202 = OpAccessChain %7 %45 %201
        %203 = OpLoad %6 %202
        %204 = OpAccessChain %7 %45 %199
               OpStore %204 %203
        %206 = OpIMul %6 %11 %217
        %208 = OpIMul %6 %197 %220
        %209 = OpAccessChain %7 %45 %208
        %210 = OpLoad %6 %209
        %211 = OpAccessChain %7 %45 %206
               OpStore %211 %210
               OpBranch %38
         %38 = OpLabel
        %214 = OpIAdd %6 %220 %213
               OpStore %34 %214
               OpBranch %35
         %37 = OpLabel
               OpBranch %28
         %28 = OpLabel
        %216 = OpIAdd %6 %217 %213
               OpStore %23 %216
               OpBranch %25
         %27 = OpLabel
               OpReturn
               OpFunctionEnd
)";

  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  const Function* f = spvtest::GetFunction(module, 4);
  LoopDescriptor& ld = *context->GetLoopDescriptor(f);

  std::vector<const Loop*> loops{&ld.GetLoopByIndex(0), &ld.GetLoopByIndex(1)};

  LoopDependenceAnalysis analysis{context.get(), loops};

  const int instructions_expected = 17;
  const Instruction* store[instructions_expected];
  const Instruction* load[instructions_expected];
  int stores_found = 0;
  int loads_found = 0;

  int block_id = 36;
  ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));

  for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
    if (inst.opcode() == spv::Op::OpStore) {
      store[stores_found] = &inst;
      ++stores_found;
    }

    if (inst.opcode() == spv::Op::OpLoad) {
      load[loads_found] = &inst;
      ++loads_found;
    }
  }

  EXPECT_EQ(instructions_expected, stores_found);
  EXPECT_EQ(instructions_expected, loads_found);

  auto directions_all = DistanceEntry(DistanceEntry::Directions::ALL);
  auto directions_none = DistanceEntry(DistanceEntry::Directions::NONE);

  auto dependent = DistanceVector({directions_all, directions_all});
  auto independent = DistanceVector({directions_none, directions_none});

  CheckDependenceAndDirection(load[0], store[0], false, dependent, &analysis);
  CheckDependenceAndDirection(load[1], store[1], false, dependent, &analysis);
  CheckDependenceAndDirection(load[2], store[2], false, dependent, &analysis);
  CheckDependenceAndDirection(load[3], store[3], false, dependent, &analysis);
  CheckDependenceAndDirection(load[4], store[4], true, independent, &analysis);
  CheckDependenceAndDirection(load[5], store[5], false, dependent, &analysis);
  CheckDependenceAndDirection(load[6], store[6], false, dependent, &analysis);
  CheckDependenceAndDirection(load[7], store[7], false, dependent, &analysis);
  CheckDependenceAndDirection(load[8], store[8], true, independent, &analysis);
  CheckDependenceAndDirection(load[9], store[9], false, dependent, &analysis);
  CheckDependenceAndDirection(load[10], store[10], false, dependent, &analysis);
  CheckDependenceAndDirection(load[11], store[11], false, dependent, &analysis);
  CheckDependenceAndDirection(load[12], store[12], false, dependent, &analysis);
  CheckDependenceAndDirection(load[13], store[13], true, independent,
                              &analysis);
  CheckDependenceAndDirection(load[14], store[14], true, independent,
                              &analysis);
  CheckDependenceAndDirection(load[15], store[15], true, independent,
                              &analysis);
  CheckDependenceAndDirection(load[16], store[16], true, independent,
                              &analysis);
}

void PartitionSubscripts(const Instruction* instruction_0,
                         const Instruction* instruction_1,
                         LoopDependenceAnalysis* analysis,
                         std::vector<std::vector<int>> expected_ids) {
  auto subscripts_0 = analysis->GetSubscripts(instruction_0);
  auto subscripts_1 = analysis->GetSubscripts(instruction_1);

  std::vector<std::set<std::pair<Instruction*, Instruction*>>>
      expected_partition{};

  for (const auto& partition : expected_ids) {
    expected_partition.push_back(
        std::set<std::pair<Instruction*, Instruction*>>{});
    for (auto id : partition) {
      expected_partition.back().insert({subscripts_0[id], subscripts_1[id]});
    }
  }

  EXPECT_EQ(expected_partition,
            analysis->PartitionSubscripts(subscripts_0, subscripts_1));
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store
#version 440 core
void main(){
  int[10][10][10][10] arr;
  for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
      for (int k = 0; k < 10; k++) {
        for (int l = 0; l < 10; l++) {
          arr[i][j][k][l] = arr[i][j][k][l]; // 0, all independent
          arr[i][j][k][l] = arr[i][j][l][0]; // 1, last 2 coupled
          arr[i][j][k][l] = arr[j][i][k][l]; // 2, first 2 coupled
          arr[i][j][k][l] = arr[l][j][k][i]; // 3, first & last coupled
          arr[i][j][k][l] = arr[i][k][j][l]; // 4, middle 2 coupled
          arr[i+j][j][k][l] = arr[i][j][k][l]; // 5, first 2 coupled
          arr[i+j+k][j][k][l] = arr[i][j][k][l]; // 6, first 3 coupled
          arr[i+j+k+l][j][k][l] = arr[i][j][k][l]; // 7, all 4 coupled
          arr[i][j][k][l] = arr[i][l][j][k]; // 8, last 3 coupled
          arr[i][j-k][k][l] = arr[i][j][l][k]; // 9, last 3 coupled
          arr[i][j][k][l] = arr[l][i][j][k]; // 10, all 4 coupled
          arr[i][j][k][l] = arr[j][i][l][k]; // 11, 2 coupled partitions (i,j) &
(l&k)
          arr[i][j][k][l] = arr[k][l][i][j]; // 12, 2 coupled partitions (i,k) &
(j&l)
        }
      }
    }
  }
}
*/
TEST(DependencyAnalysis, SubscriptPartitioning) {
  const std::string text = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %8 "i"
               OpName %19 "j"
               OpName %27 "k"
               OpName %35 "l"
               OpName %50 "arr"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
          %6 = OpTypeInt 32 1
          %7 = OpTypePointer Function %6
          %9 = OpConstant %6 0
         %16 = OpConstant %6 10
         %17 = OpTypeBool
         %43 = OpTypeInt 32 0
         %44 = OpConstant %43 10
         %45 = OpTypeArray %6 %44
         %46 = OpTypeArray %45 %44
         %47 = OpTypeArray %46 %44
         %48 = OpTypeArray %47 %44
         %49 = OpTypePointer Function %48
        %208 = OpConstant %6 1
        %217 = OpUndef %6
          %4 = OpFunction %2 None %3
          %5 = OpLabel
          %8 = OpVariable %7 Function
         %19 = OpVariable %7 Function
         %27 = OpVariable %7 Function
         %35 = OpVariable %7 Function
         %50 = OpVariable %49 Function
               OpStore %8 %9
               OpBranch %10
         %10 = OpLabel
        %216 = OpPhi %6 %9 %5 %215 %13
        %218 = OpPhi %6 %217 %5 %221 %13
        %219 = OpPhi %6 %217 %5 %222 %13
        %220 = OpPhi %6 %217 %5 %223 %13
               OpLoopMerge %12 %13 None
               OpBranch %14
         %14 = OpLabel
         %18 = OpSLessThan %17 %216 %16
               OpBranchConditional %18 %11 %12
         %11 = OpLabel
               OpStore %19 %9
               OpBranch %20
         %20 = OpLabel
        %221 = OpPhi %6 %9 %11 %213 %23
        %222 = OpPhi %6 %219 %11 %224 %23
        %223 = OpPhi %6 %220 %11 %225 %23
               OpLoopMerge %22 %23 None
               OpBranch %24
         %24 = OpLabel
         %26 = OpSLessThan %17 %221 %16
               OpBranchConditional %26 %21 %22
         %21 = OpLabel
               OpStore %27 %9
               OpBranch %28
         %28 = OpLabel
        %224 = OpPhi %6 %9 %21 %211 %31
        %225 = OpPhi %6 %223 %21 %226 %31
               OpLoopMerge %30 %31 None
               OpBranch %32
         %32 = OpLabel
         %34 = OpSLessThan %17 %224 %16
               OpBranchConditional %34 %29 %30
         %29 = OpLabel
               OpStore %35 %9
               OpBranch %36
         %36 = OpLabel
        %226 = OpPhi %6 %9 %29 %209 %39
               OpLoopMerge %38 %39 None
               OpBranch %40
         %40 = OpLabel
         %42 = OpSLessThan %17 %226 %16
               OpBranchConditional %42 %37 %38
         %37 = OpLabel
         %59 = OpAccessChain %7 %50 %216 %221 %224 %226
         %60 = OpLoad %6 %59
         %61 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %61 %60
         %69 = OpAccessChain %7 %50 %216 %221 %226 %9
         %70 = OpLoad %6 %69
         %71 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %71 %70
         %80 = OpAccessChain %7 %50 %221 %216 %224 %226
         %81 = OpLoad %6 %80
         %82 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %82 %81
         %91 = OpAccessChain %7 %50 %226 %221 %224 %216
         %92 = OpLoad %6 %91
         %93 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %93 %92
        %102 = OpAccessChain %7 %50 %216 %224 %221 %226
        %103 = OpLoad %6 %102
        %104 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %104 %103
        %107 = OpIAdd %6 %216 %221
        %115 = OpAccessChain %7 %50 %216 %221 %224 %226
        %116 = OpLoad %6 %115
        %117 = OpAccessChain %7 %50 %107 %221 %224 %226
               OpStore %117 %116
        %120 = OpIAdd %6 %216 %221
        %122 = OpIAdd %6 %120 %224
        %130 = OpAccessChain %7 %50 %216 %221 %224 %226
        %131 = OpLoad %6 %130
        %132 = OpAccessChain %7 %50 %122 %221 %224 %226
               OpStore %132 %131
        %135 = OpIAdd %6 %216 %221
        %137 = OpIAdd %6 %135 %224
        %139 = OpIAdd %6 %137 %226
        %147 = OpAccessChain %7 %50 %216 %221 %224 %226
        %148 = OpLoad %6 %147
        %149 = OpAccessChain %7 %50 %139 %221 %224 %226
               OpStore %149 %148
        %158 = OpAccessChain %7 %50 %216 %226 %221 %224
        %159 = OpLoad %6 %158
        %160 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %160 %159
        %164 = OpISub %6 %221 %224
        %171 = OpAccessChain %7 %50 %216 %221 %226 %224
        %172 = OpLoad %6 %171
        %173 = OpAccessChain %7 %50 %216 %164 %224 %226
               OpStore %173 %172
        %182 = OpAccessChain %7 %50 %226 %216 %221 %224
        %183 = OpLoad %6 %182
        %184 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %184 %183
        %193 = OpAccessChain %7 %50 %221 %216 %226 %224
        %194 = OpLoad %6 %193
        %195 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %195 %194
        %204 = OpAccessChain %7 %50 %224 %226 %216 %221
        %205 = OpLoad %6 %204
        %206 = OpAccessChain %7 %50 %216 %221 %224 %226
               OpStore %206 %205
               OpBranch %39
         %39 = OpLabel
        %209 = OpIAdd %6 %226 %208
               OpStore %35 %209
               OpBranch %36
         %38 = OpLabel
               OpBranch %31
         %31 = OpLabel
        %211 = OpIAdd %6 %224 %208
               OpStore %27 %211
               OpBranch %28
         %30 = OpLabel
               OpBranch %23
         %23 = OpLabel
        %213 = OpIAdd %6 %221 %208
               OpStore %19 %213
               OpBranch %20
         %22 = OpLabel
               OpBranch %13
         %13 = OpLabel
        %215 = OpIAdd %6 %216 %208
               OpStore %8 %215
               OpBranch %10
         %12 = OpLabel
               OpReturn
               OpFunctionEnd
  )";

  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;
  const Function* f = spvtest::GetFunction(module, 4);
  LoopDescriptor& ld = *context->GetLoopDescriptor(f);

  std::vector<const Loop*> loop_nest{
      &ld.GetLoopByIndex(0), &ld.GetLoopByIndex(1), &ld.GetLoopByIndex(2),
      &ld.GetLoopByIndex(3)};
  LoopDependenceAnalysis analysis{context.get(), loop_nest};

  const int instructions_expected = 13;
  const Instruction* store[instructions_expected];
  const Instruction* load[instructions_expected];
  int stores_found = 0;
  int loads_found = 0;

  int block_id = 37;
  ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));

  for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
    if (inst.opcode() == spv::Op::OpStore) {
      store[stores_found] = &inst;
      ++stores_found;
    }

    if (inst.opcode() == spv::Op::OpLoad) {
      load[loads_found] = &inst;
      ++loads_found;
    }
  }

  EXPECT_EQ(instructions_expected, stores_found);
  EXPECT_EQ(instructions_expected, loads_found);

  PartitionSubscripts(load[0], store[0], &analysis, {{0}, {1}, {2}, {3}});
  PartitionSubscripts(load[1], store[1], &analysis, {{0}, {1}, {2, 3}});
  PartitionSubscripts(load[2], store[2], &analysis, {{0, 1}, {2}, {3}});
  PartitionSubscripts(load[3], store[3], &analysis, {{0, 3}, {1}, {2}});
  PartitionSubscripts(load[4], store[4], &analysis, {{0}, {1, 2}, {3}});
  PartitionSubscripts(load[5], store[5], &analysis, {{0, 1}, {2}, {3}});
  PartitionSubscripts(load[6], store[6], &analysis, {{0, 1, 2}, {3}});
  PartitionSubscripts(load[7], store[7], &analysis, {{0, 1, 2, 3}});
  PartitionSubscripts(load[8], store[8], &analysis, {{0}, {1, 2, 3}});
  PartitionSubscripts(load[9], store[9], &analysis, {{0}, {1, 2, 3}});
  PartitionSubscripts(load[10], store[10], &analysis, {{0, 1, 2, 3}});
  PartitionSubscripts(load[11], store[11], &analysis, {{0, 1}, {2, 3}});
  PartitionSubscripts(load[12], store[12], &analysis, {{0, 2}, {1, 3}});
}

/*
  Generated from the following GLSL fragment shader
  with --eliminate-local-multi-store

#version 440 core
void a() {
  int[10][10] arr;
  for (int i = 0; i < 10; ++i) {
    for (int j = 0; j < 10; ++j) {
      // Dependent, distance vector (1, -1)
      arr[i+1][i+j] = arr[i][i+j];
    }
  }
}

void b() {
  int[10][10] arr;
  for (int i = 0; i < 10; ++i) {
    // Independent
    arr[i+1][i+2] = arr[i][i] + 2;
  }
}

void c() {
  int[10][10] arr;
  for (int i = 0; i < 10; ++i) {
    // Dependence point (1,2)
    arr[i][i] = arr[1][i-1] + 2;
  }
}

void d() {
  int[10][10][10] arr;
  for (int i = 0; i < 10; ++i) {
    for (int j = 0; j < 10; ++j) {
      for (int k = 0; k < 10; ++k) {
        // Dependent, distance vector (1,1,-1)
        arr[j-i][i+1][j+k] = arr[j-i][i][j+k];
      }
    }
  }
}

void e() {
  int[10][10] arr;
  for (int i = 0; i < 10; ++i) {
    for (int j = 0; j < 10; ++j) {
      // Independent with GCD after propagation
      arr[i][2*j+i] = arr[i][2*j-i+5];
    }
  }
}

void main(){
  a();
  b();
  c();
  d();
  e();
}
*/
TEST(DependencyAnalysis, Delta) {
  const std::string text = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %4 "main"
               OpExecutionMode %4 OriginUpperLeft
               OpSource GLSL 440
               OpName %4 "main"
               OpName %6 "a("
               OpName %8 "b("
               OpName %10 "c("
               OpName %12 "d("
               OpName %14 "e("
               OpName %18 "i"
               OpName %29 "j"
               OpName %42 "arr"
               OpName %60 "i"
               OpName %68 "arr"
               OpName %82 "i"
               OpName %90 "arr"
               OpName %101 "i"
               OpName %109 "j"
               OpName %117 "k"
               OpName %127 "arr"
               OpName %152 "i"
               OpName %160 "j"
               OpName %168 "arr"
          %2 = OpTypeVoid
          %3 = OpTypeFunction %2
         %16 = OpTypeInt 32 1
         %17 = OpTypePointer Function %16
         %19 = OpConstant %16 0
         %26 = OpConstant %16 10
         %27 = OpTypeBool
         %37 = OpTypeInt 32 0
         %38 = OpConstant %37 10
         %39 = OpTypeArray %16 %38
         %40 = OpTypeArray %39 %38
         %41 = OpTypePointer Function %40
         %44 = OpConstant %16 1
         %72 = OpConstant %16 2
        %125 = OpTypeArray %40 %38
        %126 = OpTypePointer Function %125
        %179 = OpConstant %16 5
          %4 = OpFunction %2 None %3
          %5 = OpLabel
        %188 = OpFunctionCall %2 %6
        %189 = OpFunctionCall %2 %8
        %190 = OpFunctionCall %2 %10
        %191 = OpFunctionCall %2 %12
        %192 = OpFunctionCall %2 %14
               OpReturn
               OpFunctionEnd
          %6 = OpFunction %2 None %3
          %7 = OpLabel
         %18 = OpVariable %17 Function
         %29 = OpVariable %17 Function
         %42 = OpVariable %41 Function
               OpStore %18 %19
               OpBranch %20
         %20 = OpLabel
        %193 = OpPhi %16 %19 %7 %59 %23
               OpLoopMerge %22 %23 None
               OpBranch %24
         %24 = OpLabel
         %28 = OpSLessThan %27 %193 %26
               OpBranchConditional %28 %21 %22
         %21 = OpLabel
               OpStore %29 %19
               OpBranch %30
         %30 = OpLabel
        %194 = OpPhi %16 %19 %21 %57 %33
               OpLoopMerge %32 %33 None
               OpBranch %34
         %34 = OpLabel
         %36 = OpSLessThan %27 %194 %26
               OpBranchConditional %36 %31 %32
         %31 = OpLabel
         %45 = OpIAdd %16 %193 %44
         %48 = OpIAdd %16 %193 %194
         %52 = OpIAdd %16 %193 %194
         %53 = OpAccessChain %17 %42 %193 %52
         %54 = OpLoad %16 %53
         %55 = OpAccessChain %17 %42 %45 %48
               OpStore %55 %54
               OpBranch %33
         %33 = OpLabel
         %57 = OpIAdd %16 %194 %44
               OpStore %29 %57
               OpBranch %30
         %32 = OpLabel
               OpBranch %23
         %23 = OpLabel
         %59 = OpIAdd %16 %193 %44
               OpStore %18 %59
               OpBranch %20
         %22 = OpLabel
               OpReturn
               OpFunctionEnd
          %8 = OpFunction %2 None %3
          %9 = OpLabel
         %60 = OpVariable %17 Function
         %68 = OpVariable %41 Function
               OpStore %60 %19
               OpBranch %61
         %61 = OpLabel
        %196 = OpPhi %16 %19 %9 %81 %64
               OpLoopMerge %63 %64 None
               OpBranch %65
         %65 = OpLabel
         %67 = OpSLessThan %27 %196 %26
               OpBranchConditional %67 %62 %63
         %62 = OpLabel
         %70 = OpIAdd %16 %196 %44
         %73 = OpIAdd %16 %196 %72
         %76 = OpAccessChain %17 %68 %196 %196
         %77 = OpLoad %16 %76
         %78 = OpIAdd %16 %77 %72
         %79 = OpAccessChain %17 %68 %70 %73
               OpStore %79 %78
               OpBranch %64
         %64 = OpLabel
         %81 = OpIAdd %16 %196 %44
               OpStore %60 %81
               OpBranch %61
         %63 = OpLabel
               OpReturn
               OpFunctionEnd
         %10 = OpFunction %2 None %3
         %11 = OpLabel
         %82 = OpVariable %17 Function
         %90 = OpVariable %41 Function
               OpStore %82 %19
               OpBranch %83
         %83 = OpLabel
        %197 = OpPhi %16 %19 %11 %100 %86
               OpLoopMerge %85 %86 None
               OpBranch %87
         %87 = OpLabel
         %89 = OpSLessThan %27 %197 %26
               OpBranchConditional %89 %84 %85
         %84 = OpLabel
         %94 = OpISub %16 %197 %44
         %95 = OpAccessChain %17 %90 %44 %94
         %96 = OpLoad %16 %95
         %97 = OpIAdd %16 %96 %72
         %98 = OpAccessChain %17 %90 %197 %197
               OpStore %98 %97
               OpBranch %86
         %86 = OpLabel
        %100 = OpIAdd %16 %197 %44
               OpStore %82 %100
               OpBranch %83
         %85 = OpLabel
               OpReturn
               OpFunctionEnd
         %12 = OpFunction %2 None %3
         %13 = OpLabel
        %101 = OpVariable %17 Function
        %109 = OpVariable %17 Function
        %117 = OpVariable %17 Function
        %127 = OpVariable %126 Function
               OpStore %101 %19
               OpBranch %102
        %102 = OpLabel
        %198 = OpPhi %16 %19 %13 %151 %105
               OpLoopMerge %104 %105 None
               OpBranch %106
        %106 = OpLabel
        %108 = OpSLessThan %27 %198 %26
               OpBranchConditional %108 %103 %104
        %103 = OpLabel
               OpStore %109 %19
               OpBranch %110
        %110 = OpLabel
        %199 = OpPhi %16 %19 %103 %149 %113
               OpLoopMerge %112 %113 None
               OpBranch %114
        %114 = OpLabel
        %116 = OpSLessThan %27 %199 %26
               OpBranchConditional %116 %111 %112
        %111 = OpLabel
               OpStore %117 %19
               OpBranch %118
        %118 = OpLabel
        %201 = OpPhi %16 %19 %111 %147 %121
               OpLoopMerge %120 %121 None
               OpBranch %122
        %122 = OpLabel
        %124 = OpSLessThan %27 %201 %26
               OpBranchConditional %124 %119 %120
        %119 = OpLabel
        %130 = OpISub %16 %199 %198
        %132 = OpIAdd %16 %198 %44
        %135 = OpIAdd %16 %199 %201
        %138 = OpISub %16 %199 %198
        %142 = OpIAdd %16 %199 %201
        %143 = OpAccessChain %17 %127 %138 %198 %142
        %144 = OpLoad %16 %143
        %145 = OpAccessChain %17 %127 %130 %132 %135
               OpStore %145 %144
               OpBranch %121
        %121 = OpLabel
        %147 = OpIAdd %16 %201 %44
               OpStore %117 %147
               OpBranch %118
        %120 = OpLabel
               OpBranch %113
        %113 = OpLabel
        %149 = OpIAdd %16 %199 %44
               OpStore %109 %149
               OpBranch %110
        %112 = OpLabel
               OpBranch %105
        %105 = OpLabel
        %151 = OpIAdd %16 %198 %44
               OpStore %101 %151
               OpBranch %102
        %104 = OpLabel
               OpReturn
               OpFunctionEnd
         %14 = OpFunction %2 None %3
         %15 = OpLabel
        %152 = OpVariable %17 Function
        %160 = OpVariable %17 Function
        %168 = OpVariable %41 Function
               OpStore %152 %19
               OpBranch %153
        %153 = OpLabel
        %204 = OpPhi %16 %19 %15 %187 %156
               OpLoopMerge %155 %156 None
               OpBranch %157
        %157 = OpLabel
        %159 = OpSLessThan %27 %204 %26
               OpBranchConditional %159 %154 %155
        %154 = OpLabel
               OpStore %160 %19
               OpBranch %161
        %161 = OpLabel
        %205 = OpPhi %16 %19 %154 %185 %164
               OpLoopMerge %163 %164 None
               OpBranch %165
        %165 = OpLabel
        %167 = OpSLessThan %27 %205 %26
               OpBranchConditional %167 %162 %163
        %162 = OpLabel
        %171 = OpIMul %16 %72 %205
        %173 = OpIAdd %16 %171 %204
        %176 = OpIMul %16 %72 %205
        %178 = OpISub %16 %176 %204
        %180 = OpIAdd %16 %178 %179
        %181 = OpAccessChain %17 %168 %204 %180
        %182 = OpLoad %16 %181
        %183 = OpAccessChain %17 %168 %204 %173
               OpStore %183 %182
               OpBranch %164
        %164 = OpLabel
        %185 = OpIAdd %16 %205 %44
               OpStore %160 %185
               OpBranch %161
        %163 = OpLabel
               OpBranch %156
        %156 = OpLabel
        %187 = OpIAdd %16 %204 %44
               OpStore %152 %187
               OpBranch %153
        %155 = OpLabel
               OpReturn
               OpFunctionEnd
    )";

  std::unique_ptr<IRContext> context =
      BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text,
                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
  ASSERT_NE(nullptr, context);
  Module* module = context->module();
  EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n"
                             << text << std::endl;

  {
    const Function* f = spvtest::GetFunction(module, 6);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    const Instruction* store = nullptr;
    const Instruction* load = nullptr;

    int block_id = 31;
    ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));

    for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }

      if (inst.opcode() == spv::Op::OpLoad) {
        load = &inst;
      }
    }

    EXPECT_NE(nullptr, store);
    EXPECT_NE(nullptr, load);

    std::vector<const Loop*> loop_nest{&ld.GetLoopByIndex(0),
                                       &ld.GetLoopByIndex(1)};
    LoopDependenceAnalysis analysis{context.get(), loop_nest};

    DistanceVector dv_entry(loop_nest.size());

    std::vector<DistanceEntry> expected_entries{
        DistanceEntry(DistanceEntry::Directions::LT, 1),
        DistanceEntry(DistanceEntry::Directions::LT, 1)};

    DistanceVector expected_distance_vector(expected_entries);

    auto is_independent = analysis.GetDependence(load, store, &dv_entry);

    EXPECT_FALSE(is_independent);
    EXPECT_EQ(expected_distance_vector, dv_entry);
  }

  {
    const Function* f = spvtest::GetFunction(module, 8);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    const Instruction* store = nullptr;
    const Instruction* load = nullptr;

    int block_id = 62;
    ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));

    for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }

      if (inst.opcode() == spv::Op::OpLoad) {
        load = &inst;
      }
    }

    EXPECT_NE(nullptr, store);
    EXPECT_NE(nullptr, load);

    std::vector<const Loop*> loop_nest{&ld.GetLoopByIndex(0)};
    LoopDependenceAnalysis analysis{context.get(), loop_nest};

    DistanceVector dv_entry(loop_nest.size());
    auto is_independent = analysis.GetDependence(load, store, &dv_entry);

    EXPECT_TRUE(is_independent);
  }

  {
    const Function* f = spvtest::GetFunction(module, 10);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    const Instruction* store = nullptr;
    const Instruction* load = nullptr;

    int block_id = 84;
    ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));

    for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }

      if (inst.opcode() == spv::Op::OpLoad) {
        load = &inst;
      }
    }

    EXPECT_NE(nullptr, store);
    EXPECT_NE(nullptr, load);

    std::vector<const Loop*> loop_nest{&ld.GetLoopByIndex(0)};
    LoopDependenceAnalysis analysis{context.get(), loop_nest};

    DistanceVector dv_entry(loop_nest.size());
    auto is_independent = analysis.GetDependence(load, store, &dv_entry);

    DistanceVector expected_distance_vector({DistanceEntry(1, 2)});

    EXPECT_FALSE(is_independent);
    EXPECT_EQ(expected_distance_vector, dv_entry);
  }

  {
    const Function* f = spvtest::GetFunction(module, 12);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    const Instruction* store = nullptr;
    const Instruction* load = nullptr;

    int block_id = 119;
    ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));

    for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }

      if (inst.opcode() == spv::Op::OpLoad) {
        load = &inst;
      }
    }

    EXPECT_NE(nullptr, store);
    EXPECT_NE(nullptr, load);

    std::vector<const Loop*> loop_nest{
        &ld.GetLoopByIndex(0), &ld.GetLoopByIndex(1), &ld.GetLoopByIndex(2)};
    LoopDependenceAnalysis analysis{context.get(), loop_nest};

    DistanceVector dv_entry(loop_nest.size());

    std::vector<DistanceEntry> expected_entries{
        DistanceEntry(DistanceEntry::Directions::LT, 1),
        DistanceEntry(DistanceEntry::Directions::LT, 1),
        DistanceEntry(DistanceEntry::Directions::GT, -1)};

    DistanceVector expected_distance_vector(expected_entries);

    auto is_independent = analysis.GetDependence(store, load, &dv_entry);

    EXPECT_FALSE(is_independent);
    EXPECT_EQ(expected_distance_vector, dv_entry);
  }

  {
    const Function* f = spvtest::GetFunction(module, 14);
    LoopDescriptor& ld = *context->GetLoopDescriptor(f);

    const Instruction* store = nullptr;
    const Instruction* load = nullptr;

    int block_id = 162;
    ASSERT_TRUE(spvtest::GetBasicBlock(f, block_id));

    for (const Instruction& inst : *spvtest::GetBasicBlock(f, block_id)) {
      if (inst.opcode() == spv::Op::OpStore) {
        store = &inst;
      }

      if (inst.opcode() == spv::Op::OpLoad) {
        load = &inst;
      }
    }

    EXPECT_NE(nullptr, store);
    EXPECT_NE(nullptr, load);

    std::vector<const Loop*> loop_nest{&ld.GetLoopByIndex(0),
                                       &ld.GetLoopByIndex(1)};
    LoopDependenceAnalysis analysis{context.get(), loop_nest};

    DistanceVector dv_entry(loop_nest.size());
    auto is_independent = analysis.GetDependence(load, store, &dv_entry);

    EXPECT_TRUE(is_independent);
  }
}

TEST(DependencyAnalysis, ConstraintIntersection) {
  LoopDependenceAnalysis analysis{nullptr, std::vector<const Loop*>{}};
  auto scalar_evolution = analysis.GetScalarEvolution();
  {
    // One is none. Other should be returned
    auto none = analysis.make_constraint<DependenceNone>();
    auto x = scalar_evolution->CreateConstant(1);
    auto y = scalar_evolution->CreateConstant(10);
    auto point = analysis.make_constraint<DependencePoint>(x, y, nullptr);

    auto ret_0 = analysis.IntersectConstraints(none, point, nullptr, nullptr);

    auto ret_point_0 = ret_0->AsDependencePoint();
    ASSERT_NE(nullptr, ret_point_0);
    EXPECT_EQ(*x, *ret_point_0->GetSource());
    EXPECT_EQ(*y, *ret_point_0->GetDestination());

    auto ret_1 = analysis.IntersectConstraints(point, none, nullptr, nullptr);

    auto ret_point_1 = ret_1->AsDependencePoint();
    ASSERT_NE(nullptr, ret_point_1);
    EXPECT_EQ(*x, *ret_point_1->GetSource());
    EXPECT_EQ(*y, *ret_point_1->GetDestination());
  }

  {
    // Both distances
    auto x = scalar_evolution->CreateConstant(1);
    auto y = scalar_evolution->CreateConstant(10);

    auto distance_0 = analysis.make_constraint<DependenceDistance>(x, nullptr);
    auto distance_1 = analysis.make_constraint<DependenceDistance>(y, nullptr);

    // Equal distances
    auto ret_0 =
        analysis.IntersectConstraints(distance_1, distance_1, nullptr, nullptr);

    auto ret_distance = ret_0->AsDependenceDistance();
    ASSERT_NE(nullptr, ret_distance);
    EXPECT_EQ(*y, *ret_distance->GetDistance());

    // Non-equal distances
    auto ret_1 =
        analysis.IntersectConstraints(distance_0, distance_1, nullptr, nullptr);
    EXPECT_NE(nullptr, ret_1->AsDependenceEmpty());
  }

  {
    // Both points
    auto x = scalar_evolution->CreateConstant(1);
    auto y = scalar_evolution->CreateConstant(10);

    auto point_0 = analysis.make_constraint<DependencePoint>(x, y, nullptr);
    auto point_1 = analysis.make_constraint<DependencePoint>(x, y, nullptr);
    auto point_2 = analysis.make_constraint<DependencePoint>(y, y, nullptr);

    // Equal points
    auto ret_0 =
        analysis.IntersectConstraints(point_0, point_1, nullptr, nullptr);
    auto ret_point_0 = ret_0->AsDependencePoint();
    ASSERT_NE(nullptr, ret_point_0);
    EXPECT_EQ(*x, *ret_point_0->GetSource());
    EXPECT_EQ(*y, *ret_point_0->GetDestination());

    // Non-equal points
    auto ret_1 =
        analysis.IntersectConstraints(point_0, point_2, nullptr, nullptr);
    EXPECT_NE(nullptr, ret_1->AsDependenceEmpty());
  }

  {
    // Both lines, parallel
    auto a0 = scalar_evolution->CreateConstant(3);
    auto b0 = scalar_evolution->CreateConstant(6);
    auto c0 = scalar_evolution->CreateConstant(9);

    auto a1 = scalar_evolution->CreateConstant(6);
    auto b1 = scalar_evolution->CreateConstant(12);
    auto c1 = scalar_evolution->CreateConstant(18);

    auto line_0 = analysis.make_constraint<DependenceLine>(a0, b0, c0, nullptr);
    auto line_1 = analysis.make_constraint<DependenceLine>(a1, b1, c1, nullptr);

    // Same line, both ways
    auto ret_0 =
        analysis.IntersectConstraints(line_0, line_1, nullptr, nullptr);
    auto ret_1 =
        analysis.IntersectConstraints(line_1, line_0, nullptr, nullptr);

    auto ret_line_0 = ret_0->AsDependenceLine();
    auto ret_line_1 = ret_1->AsDependenceLine();

    EXPECT_NE(nullptr, ret_line_0);
    EXPECT_NE(nullptr, ret_line_1);

    // Non-intersecting parallel lines
    auto c2 = scalar_evolution->CreateConstant(12);
    auto line_2 = analysis.make_constraint<DependenceLine>(a1, b1, c2, nullptr);

    auto ret_2 =
        analysis.IntersectConstraints(line_0, line_2, nullptr, nullptr);
    auto ret_3 =
        analysis.IntersectConstraints(line_2, line_0, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_2->AsDependenceEmpty());
    EXPECT_NE(nullptr, ret_3->AsDependenceEmpty());

    auto c3 = scalar_evolution->CreateConstant(20);
    auto line_3 = analysis.make_constraint<DependenceLine>(a1, b1, c3, nullptr);

    auto ret_4 =
        analysis.IntersectConstraints(line_0, line_3, nullptr, nullptr);
    auto ret_5 =
        analysis.IntersectConstraints(line_3, line_0, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_4->AsDependenceEmpty());
    EXPECT_NE(nullptr, ret_5->AsDependenceEmpty());
  }

  {
    // Non-constant line
    auto unknown = scalar_evolution->CreateCantComputeNode();
    auto constant = scalar_evolution->CreateConstant(10);

    auto line_0 = analysis.make_constraint<DependenceLine>(constant, constant,
                                                           constant, nullptr);
    auto line_1 = analysis.make_constraint<DependenceLine>(unknown, unknown,
                                                           unknown, nullptr);

    auto ret_0 =
        analysis.IntersectConstraints(line_0, line_1, nullptr, nullptr);
    auto ret_1 =
        analysis.IntersectConstraints(line_1, line_0, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_0->AsDependenceNone());
    EXPECT_NE(nullptr, ret_1->AsDependenceNone());
  }

  {
    auto bound_0 = scalar_evolution->CreateConstant(0);
    auto bound_1 = scalar_evolution->CreateConstant(20);

    auto a0 = scalar_evolution->CreateConstant(1);
    auto b0 = scalar_evolution->CreateConstant(2);
    auto c0 = scalar_evolution->CreateConstant(6);

    auto a1 = scalar_evolution->CreateConstant(-1);
    auto b1 = scalar_evolution->CreateConstant(2);
    auto c1 = scalar_evolution->CreateConstant(2);

    auto line_0 = analysis.make_constraint<DependenceLine>(a0, b0, c0, nullptr);
    auto line_1 = analysis.make_constraint<DependenceLine>(a1, b1, c1, nullptr);

    // Intersecting lines, has integer solution, in bounds
    auto ret_0 =
        analysis.IntersectConstraints(line_0, line_1, bound_0, bound_1);
    auto ret_1 =
        analysis.IntersectConstraints(line_1, line_0, bound_0, bound_1);

    auto ret_point_0 = ret_0->AsDependencePoint();
    auto ret_point_1 = ret_1->AsDependencePoint();

    EXPECT_NE(nullptr, ret_point_0);
    EXPECT_NE(nullptr, ret_point_1);

    auto const_2 = scalar_evolution->CreateConstant(2);

    EXPECT_EQ(*const_2, *ret_point_0->GetSource());
    EXPECT_EQ(*const_2, *ret_point_0->GetDestination());

    EXPECT_EQ(*const_2, *ret_point_1->GetSource());
    EXPECT_EQ(*const_2, *ret_point_1->GetDestination());

    // Intersecting lines, has integer solution, out of bounds
    auto ret_2 =
        analysis.IntersectConstraints(line_0, line_1, bound_0, bound_0);
    auto ret_3 =
        analysis.IntersectConstraints(line_1, line_0, bound_0, bound_0);

    EXPECT_NE(nullptr, ret_2->AsDependenceEmpty());
    EXPECT_NE(nullptr, ret_3->AsDependenceEmpty());

    auto a2 = scalar_evolution->CreateConstant(-4);
    auto b2 = scalar_evolution->CreateConstant(1);
    auto c2 = scalar_evolution->CreateConstant(0);

    auto a3 = scalar_evolution->CreateConstant(4);
    auto b3 = scalar_evolution->CreateConstant(1);
    auto c3 = scalar_evolution->CreateConstant(4);

    auto line_2 = analysis.make_constraint<DependenceLine>(a2, b2, c2, nullptr);
    auto line_3 = analysis.make_constraint<DependenceLine>(a3, b3, c3, nullptr);

    // Intersecting, no integer solution
    auto ret_4 =
        analysis.IntersectConstraints(line_2, line_3, bound_0, bound_1);
    auto ret_5 =
        analysis.IntersectConstraints(line_3, line_2, bound_0, bound_1);

    EXPECT_NE(nullptr, ret_4->AsDependenceEmpty());
    EXPECT_NE(nullptr, ret_5->AsDependenceEmpty());

    auto unknown = scalar_evolution->CreateCantComputeNode();

    // Non-constant bound
    auto ret_6 =
        analysis.IntersectConstraints(line_0, line_1, unknown, bound_1);
    auto ret_7 =
        analysis.IntersectConstraints(line_1, line_0, bound_0, unknown);

    EXPECT_NE(nullptr, ret_6->AsDependenceNone());
    EXPECT_NE(nullptr, ret_7->AsDependenceNone());
  }

  {
    auto constant_0 = scalar_evolution->CreateConstant(0);
    auto constant_1 = scalar_evolution->CreateConstant(1);
    auto constant_neg_1 = scalar_evolution->CreateConstant(-1);
    auto constant_2 = scalar_evolution->CreateConstant(2);
    auto constant_neg_2 = scalar_evolution->CreateConstant(-2);

    auto point_0_0 = analysis.make_constraint<DependencePoint>(
        constant_0, constant_0, nullptr);
    auto point_0_1 = analysis.make_constraint<DependencePoint>(
        constant_0, constant_1, nullptr);
    auto point_1_0 = analysis.make_constraint<DependencePoint>(
        constant_1, constant_0, nullptr);
    auto point_1_1 = analysis.make_constraint<DependencePoint>(
        constant_1, constant_1, nullptr);
    auto point_1_2 = analysis.make_constraint<DependencePoint>(
        constant_1, constant_2, nullptr);
    auto point_1_neg_1 = analysis.make_constraint<DependencePoint>(
        constant_1, constant_neg_1, nullptr);
    auto point_neg_1_1 = analysis.make_constraint<DependencePoint>(
        constant_neg_1, constant_1, nullptr);

    auto line_y_0 = analysis.make_constraint<DependenceLine>(
        constant_0, constant_1, constant_0, nullptr);
    auto line_y_1 = analysis.make_constraint<DependenceLine>(
        constant_0, constant_1, constant_1, nullptr);
    auto line_y_2 = analysis.make_constraint<DependenceLine>(
        constant_0, constant_1, constant_2, nullptr);

    // Parallel horizontal lines, y = 0 & y = 1, should return no intersection
    auto ret =
        analysis.IntersectConstraints(line_y_0, line_y_1, nullptr, nullptr);

    EXPECT_NE(nullptr, ret->AsDependenceEmpty());

    // Parallel horizontal lines, y = 1 & y = 2, should return no intersection
    auto ret_y_12 =
        analysis.IntersectConstraints(line_y_1, line_y_2, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_y_12->AsDependenceEmpty());

    // Same horizontal lines, y = 0 & y = 0, should return the line
    auto ret_y_same_0 =
        analysis.IntersectConstraints(line_y_0, line_y_0, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_y_same_0->AsDependenceLine());

    // Same horizontal lines, y = 1 & y = 1, should return the line
    auto ret_y_same_1 =
        analysis.IntersectConstraints(line_y_1, line_y_1, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_y_same_1->AsDependenceLine());

    auto line_x_0 = analysis.make_constraint<DependenceLine>(
        constant_1, constant_0, constant_0, nullptr);
    auto line_x_1 = analysis.make_constraint<DependenceLine>(
        constant_1, constant_0, constant_1, nullptr);
    auto line_x_2 = analysis.make_constraint<DependenceLine>(
        constant_1, constant_0, constant_2, nullptr);
    auto line_2x_1 = analysis.make_constraint<DependenceLine>(
        constant_2, constant_0, constant_1, nullptr);
    auto line_2x_2 = analysis.make_constraint<DependenceLine>(
        constant_2, constant_0, constant_2, nullptr);

    // Parallel vertical lines, x = 0 & x = 1, should return no intersection
    auto ret_x =
        analysis.IntersectConstraints(line_x_0, line_x_1, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_x->AsDependenceEmpty());

    // Parallel vertical lines, x = 1 & x = 2, should return no intersection
    auto ret_x_12 =
        analysis.IntersectConstraints(line_x_1, line_x_2, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_x_12->AsDependenceEmpty());

    // Parallel vertical lines, 2x = 1 & 2x = 2, should return no intersection
    auto ret_2x_2_2x_1 =
        analysis.IntersectConstraints(line_2x_2, line_2x_1, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_2x_2_2x_1->AsDependenceEmpty());

    // same line, 2x=2 & x = 1
    auto ret_2x_2_x_1 =
        analysis.IntersectConstraints(line_2x_2, line_x_1, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_2x_2_x_1->AsDependenceLine());

    // Same vertical lines, x = 0 & x = 0, should return the line
    auto ret_x_same_0 =
        analysis.IntersectConstraints(line_x_0, line_x_0, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_x_same_0->AsDependenceLine());
    // EXPECT_EQ(*line_x_0, *ret_x_same_0->AsDependenceLine());

    // Same vertical lines, x = 1 & x = 1, should return the line
    auto ret_x_same_1 =
        analysis.IntersectConstraints(line_x_1, line_x_1, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_x_same_1->AsDependenceLine());
    EXPECT_EQ(*line_x_1, *ret_x_same_1->AsDependenceLine());

    // x=1 & y = 0, intersect at (1, 0)
    auto ret_1_0 = analysis.IntersectConstraints(line_x_1, line_y_0,
                                                 constant_neg_1, constant_2);

    auto ret_point_1_0 = ret_1_0->AsDependencePoint();
    EXPECT_NE(nullptr, ret_point_1_0);
    EXPECT_EQ(*point_1_0, *ret_point_1_0);

    // x=1 & y = 1, intersect at (1, 1)
    auto ret_1_1 = analysis.IntersectConstraints(line_x_1, line_y_1,
                                                 constant_neg_1, constant_2);

    auto ret_point_1_1 = ret_1_1->AsDependencePoint();
    EXPECT_NE(nullptr, ret_point_1_1);
    EXPECT_EQ(*point_1_1, *ret_point_1_1);

    // x=0 & y = 0, intersect at (0, 0)
    auto ret_0_0 = analysis.IntersectConstraints(line_x_0, line_y_0,
                                                 constant_neg_1, constant_2);

    auto ret_point_0_0 = ret_0_0->AsDependencePoint();
    EXPECT_NE(nullptr, ret_point_0_0);
    EXPECT_EQ(*point_0_0, *ret_point_0_0);

    // x=0 & y = 1, intersect at (0, 1)
    auto ret_0_1 = analysis.IntersectConstraints(line_x_0, line_y_1,
                                                 constant_neg_1, constant_2);
    auto ret_point_0_1 = ret_0_1->AsDependencePoint();
    EXPECT_NE(nullptr, ret_point_0_1);
    EXPECT_EQ(*point_0_1, *ret_point_0_1);

    // x = 1 & y = 2
    auto ret_1_2 = analysis.IntersectConstraints(line_x_1, line_y_2,
                                                 constant_neg_1, constant_2);
    auto ret_point_1_2 = ret_1_2->AsDependencePoint();
    EXPECT_NE(nullptr, ret_point_1_2);
    EXPECT_EQ(*point_1_2, *ret_point_1_2);

    auto line_x_y_0 = analysis.make_constraint<DependenceLine>(
        constant_1, constant_1, constant_0, nullptr);
    auto line_x_y_1 = analysis.make_constraint<DependenceLine>(
        constant_1, constant_1, constant_1, nullptr);

    // x+y=0 & x=0, intersect (0, 0)
    auto ret_xy_0_x_0 = analysis.IntersectConstraints(
        line_x_y_0, line_x_0, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_xy_0_x_0->AsDependencePoint());
    EXPECT_EQ(*point_0_0, *ret_xy_0_x_0);

    // x+y=0 & y=0, intersect (0, 0)
    auto ret_xy_0_y_0 = analysis.IntersectConstraints(
        line_x_y_0, line_y_0, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_xy_0_y_0->AsDependencePoint());
    EXPECT_EQ(*point_0_0, *ret_xy_0_y_0);

    // x+y=0 & x=1, intersect (1, -1)
    auto ret_xy_0_x_1 = analysis.IntersectConstraints(
        line_x_y_0, line_x_1, constant_neg_2, constant_2);

    EXPECT_NE(nullptr, ret_xy_0_x_1->AsDependencePoint());
    EXPECT_EQ(*point_1_neg_1, *ret_xy_0_x_1);

    // x+y=0 & y=1, intersect (-1, 1)
    auto ret_xy_0_y_1 = analysis.IntersectConstraints(
        line_x_y_0, line_y_1, constant_neg_2, constant_2);

    EXPECT_NE(nullptr, ret_xy_0_y_1->AsDependencePoint());
    EXPECT_EQ(*point_neg_1_1, *ret_xy_0_y_1);

    // x=0 & x+y=0, intersect (0, 0)
    auto ret_x_0_xy_0 = analysis.IntersectConstraints(
        line_x_0, line_x_y_0, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_x_0_xy_0->AsDependencePoint());
    EXPECT_EQ(*point_0_0, *ret_x_0_xy_0);

    // y=0 & x+y=0, intersect (0, 0)
    auto ret_y_0_xy_0 = analysis.IntersectConstraints(
        line_y_0, line_x_y_0, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_y_0_xy_0->AsDependencePoint());
    EXPECT_EQ(*point_0_0, *ret_y_0_xy_0);

    // x=1 & x+y=0, intersect (1, -1)
    auto ret_x_1_xy_0 = analysis.IntersectConstraints(
        line_x_1, line_x_y_0, constant_neg_2, constant_2);

    EXPECT_NE(nullptr, ret_x_1_xy_0->AsDependencePoint());
    EXPECT_EQ(*point_1_neg_1, *ret_x_1_xy_0);

    // y=1 & x+y=0, intersect (-1, 1)
    auto ret_y_1_xy_0 = analysis.IntersectConstraints(
        line_y_1, line_x_y_0, constant_neg_2, constant_2);

    EXPECT_NE(nullptr, ret_y_1_xy_0->AsDependencePoint());
    EXPECT_EQ(*point_neg_1_1, *ret_y_1_xy_0);

    // x+y=1 & x=0, intersect (0, 1)
    auto ret_xy_1_x_0 = analysis.IntersectConstraints(
        line_x_y_1, line_x_0, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_xy_1_x_0->AsDependencePoint());
    EXPECT_EQ(*point_0_1, *ret_xy_1_x_0);

    // x+y=1 & y=0, intersect (1, 0)
    auto ret_xy_1_y_0 = analysis.IntersectConstraints(
        line_x_y_1, line_y_0, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_xy_1_y_0->AsDependencePoint());
    EXPECT_EQ(*point_1_0, *ret_xy_1_y_0);

    // x+y=1 & x=1, intersect (1, 0)
    auto ret_xy_1_x_1 = analysis.IntersectConstraints(
        line_x_y_1, line_x_1, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_xy_1_x_1->AsDependencePoint());
    EXPECT_EQ(*point_1_0, *ret_xy_1_x_1);

    // x+y=1 & y=1, intersect (0, 1)
    auto ret_xy_1_y_1 = analysis.IntersectConstraints(
        line_x_y_1, line_y_1, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_xy_1_y_1->AsDependencePoint());
    EXPECT_EQ(*point_0_1, *ret_xy_1_y_1);

    // x=0 & x+y=1, intersect (0, 1)
    auto ret_x_0_xy_1 = analysis.IntersectConstraints(
        line_x_0, line_x_y_1, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_x_0_xy_1->AsDependencePoint());
    EXPECT_EQ(*point_0_1, *ret_x_0_xy_1);

    // y=0 & x+y=1, intersect (1, 0)
    auto ret_y_0_xy_1 = analysis.IntersectConstraints(
        line_y_0, line_x_y_1, constant_neg_1, constant_2);

    EXPECT_NE(nullptr, ret_y_0_xy_1->AsDependencePoint());
    EXPECT_EQ(*point_1_0, *ret_y_0_xy_1);

    // x=1 & x+y=1, intersect (1, 0)
    auto ret_x_1_xy_1 = analysis.IntersectConstraints(
        line_x_1, line_x_y_1, constant_neg_2, constant_2);

    EXPECT_NE(nullptr, ret_x_1_xy_1->AsDependencePoint());
    EXPECT_EQ(*point_1_0, *ret_x_1_xy_1);

    // y=1 & x+y=1, intersect (0, 1)
    auto ret_y_1_xy_1 = analysis.IntersectConstraints(
        line_y_1, line_x_y_1, constant_neg_2, constant_2);

    EXPECT_NE(nullptr, ret_y_1_xy_1->AsDependencePoint());
    EXPECT_EQ(*point_0_1, *ret_y_1_xy_1);
  }

  {
    // Line and point
    auto a = scalar_evolution->CreateConstant(3);
    auto b = scalar_evolution->CreateConstant(10);
    auto c = scalar_evolution->CreateConstant(16);

    auto line = analysis.make_constraint<DependenceLine>(a, b, c, nullptr);

    // Point on line
    auto x = scalar_evolution->CreateConstant(2);
    auto y = scalar_evolution->CreateConstant(1);
    auto point_0 = analysis.make_constraint<DependencePoint>(x, y, nullptr);

    auto ret_0 = analysis.IntersectConstraints(line, point_0, nullptr, nullptr);
    auto ret_1 = analysis.IntersectConstraints(point_0, line, nullptr, nullptr);

    auto ret_point_0 = ret_0->AsDependencePoint();
    auto ret_point_1 = ret_1->AsDependencePoint();
    ASSERT_NE(nullptr, ret_point_0);
    ASSERT_NE(nullptr, ret_point_1);

    EXPECT_EQ(*x, *ret_point_0->GetSource());
    EXPECT_EQ(*y, *ret_point_0->GetDestination());

    EXPECT_EQ(*x, *ret_point_1->GetSource());
    EXPECT_EQ(*y, *ret_point_1->GetDestination());

    // Point not on line
    auto point_1 = analysis.make_constraint<DependencePoint>(a, a, nullptr);

    auto ret_2 = analysis.IntersectConstraints(line, point_1, nullptr, nullptr);
    auto ret_3 = analysis.IntersectConstraints(point_1, line, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_2->AsDependenceEmpty());
    EXPECT_NE(nullptr, ret_3->AsDependenceEmpty());

    // Non-constant
    auto unknown = scalar_evolution->CreateCantComputeNode();

    auto point_2 =
        analysis.make_constraint<DependencePoint>(unknown, x, nullptr);

    auto ret_4 = analysis.IntersectConstraints(line, point_2, nullptr, nullptr);
    auto ret_5 = analysis.IntersectConstraints(point_2, line, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_4->AsDependenceNone());
    EXPECT_NE(nullptr, ret_5->AsDependenceNone());
  }

  {
    // Distance and point
    auto d = scalar_evolution->CreateConstant(5);
    auto distance = analysis.make_constraint<DependenceDistance>(d, nullptr);

    // Point on line
    auto x = scalar_evolution->CreateConstant(10);
    auto point_0 = analysis.make_constraint<DependencePoint>(d, x, nullptr);

    auto ret_0 =
        analysis.IntersectConstraints(distance, point_0, nullptr, nullptr);
    auto ret_1 =
        analysis.IntersectConstraints(point_0, distance, nullptr, nullptr);

    auto ret_point_0 = ret_0->AsDependencePoint();
    auto ret_point_1 = ret_1->AsDependencePoint();
    ASSERT_NE(nullptr, ret_point_0);
    ASSERT_NE(nullptr, ret_point_1);

    // Point not on line
    auto point_1 = analysis.make_constraint<DependencePoint>(x, x, nullptr);

    auto ret_2 =
        analysis.IntersectConstraints(distance, point_1, nullptr, nullptr);
    auto ret_3 =
        analysis.IntersectConstraints(point_1, distance, nullptr, nullptr);

    EXPECT_NE(nullptr, ret_2->AsDependenceEmpty());
    EXPECT_NE(nullptr, ret_3->AsDependenceEmpty());

    // Non-constant
    auto unknown = scalar_evolution->CreateCantComputeNode();
    auto unknown_distance =
        analysis.make_constraint<DependenceDistance>(unknown, nullptr);

    auto ret_4 = analysis.IntersectConstraints(unknown_distance, point_1,
                                               nullptr, nullptr);
    auto ret_5 = analysis.IntersectConstraints(point_1, unknown_distance,
                                               nullptr, nullptr);

    EXPECT_NE(nullptr, ret_4->AsDependenceNone());
    EXPECT_NE(nullptr, ret_5->AsDependenceNone());
  }
}

}  // namespace
}  // namespace opt
}  // namespace spvtools
