linker: Add tests for various type comparisons (#2613)

This adds a number of tests that check that all types will match to
identically written clones during linking, including nearly every Type
and some combinations (e.g. Functions of Arrays of Floats). Intent is
for use with https://github.com/KhronosGroup/SPIRV-Tools/pull/2580,
however that PR focuses on issues with TypeArray whereas these tests are
(more) comprehensive and test more subtle (and possibly incorrect)
cases.

A number of these tests fail, many are fixed by the aforementioned PR.
Some additional tests involving TypeForwardPointer are currently
disabled as they cause assertion failures.
diff --git a/test/link/CMakeLists.txt b/test/link/CMakeLists.txt
index 06aeb91..ee41b91 100644
--- a/test/link/CMakeLists.txt
+++ b/test/link/CMakeLists.txt
@@ -23,5 +23,6 @@
        memory_model_test.cpp
        partial_linkage_test.cpp
        unique_ids_test.cpp
+       type_match_test.cpp
   LIBS SPIRV-Tools-opt SPIRV-Tools-link
 )
diff --git a/test/link/linker_fixture.h b/test/link/linker_fixture.h
index 303f1bf..7bb1223 100644
--- a/test/link/linker_fixture.h
+++ b/test/link/linker_fixture.h
@@ -19,6 +19,8 @@
 #include <string>
 #include <vector>
 
+#include "effcee/effcee.h"
+#include "re2/re2.h"
 #include "source/spirv_constant.h"
 #include "spirv-tools/linker.hpp"
 #include "test/unit_spirv.h"
@@ -80,6 +82,101 @@
     return spvtools::Link(context_, binaries, linked_binary, options);
   }
 
+  // Assembles and links a vector of SPIR-V bodies based on the |templateBody|.
+  // Template arguments to be replaced are written as {a,b,...}.
+  // SPV_ERROR_INVALID_TEXT is returned if the assembling failed for any of the
+  // resulting bodies (or errors in the template), and SPV_ERROR_INVALID_POINTER
+  // if |linked_binary| is a null pointer.
+  spv_result_t ExpandAndLink(
+      const std::string& templateBody, spvtest::Binary* linked_binary,
+      spvtools::LinkerOptions options = spvtools::LinkerOptions()) {
+    if (!linked_binary) return SPV_ERROR_INVALID_POINTER;
+
+    // Find out how many template arguments there are, we assume they all have
+    // the same number. We'll error later if they don't.
+    re2::StringPiece temp(templateBody);
+    re2::StringPiece x;
+    int cnt = 0;
+    if (!RE2::FindAndConsume(&temp, "{")) return SPV_ERROR_INVALID_TEXT;
+    while (RE2::FindAndConsume(&temp, "([,}])", &x) && x[0] == ',') cnt++;
+    cnt++;
+    if (cnt <= 1) return SPV_ERROR_INVALID_TEXT;
+
+    // Construct a regex for a single common strip and template expansion.
+    std::string regex("([^{]*){");
+    for (int i = 0; i < cnt; i++) regex += (i > 0) ? ",([^,]*)" : "([^,]*)";
+    regex += "}";
+    RE2 pattern(regex);
+
+    // Prepare the RE2::Args for processing.
+    re2::StringPiece common;
+    std::vector<re2::StringPiece> variants(cnt);
+    std::vector<RE2::Arg> args(cnt + 1);
+    args[0] = RE2::Arg(&common);
+    std::vector<RE2::Arg*> pargs(cnt + 1);
+    pargs[0] = &args[0];
+    for (int i = 0; i < cnt; i++) {
+      args[i + 1] = RE2::Arg(&variants[i]);
+      pargs[i + 1] = &args[i + 1];
+    }
+
+    // Reset and construct the bodies bit by bit.
+    std::vector<std::string> bodies(cnt);
+    re2::StringPiece temp2(templateBody);
+    while (RE2::ConsumeN(&temp2, pattern, pargs.data(), cnt + 1)) {
+      for (int i = 0; i < cnt; i++) {
+        bodies[i].append(common.begin(), common.end());
+        bodies[i].append(variants[i].begin(), variants[i].end());
+      }
+    }
+    RE2::Consume(&temp2, "([^{]*)", &common);
+    for (int i = 0; i < cnt; i++)
+      bodies[i].append(common.begin(), common.end());
+
+    // Run through the assemble and link stages of the process.
+    return AssembleAndLink(bodies, linked_binary, options);
+  }
+
+  // Expand the |templateBody| and link the results as with ExpandAndLink,
+  // then disassemble and test that the result matches the |expected|.
+  void ExpandAndCheck(
+      const std::string& templateBody, const std::string& expected,
+      const spvtools::LinkerOptions options = spvtools::LinkerOptions()) {
+    spvtest::Binary linked_binary;
+    spv_result_t res = ExpandAndLink(templateBody, &linked_binary, options);
+    EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage() << "\nExpanded from:\n"
+                                << templateBody;
+    if (res == SPV_SUCCESS) {
+      std::string result;
+      EXPECT_TRUE(
+          tools_.Disassemble(linked_binary, &result, disassemble_options_))
+          << GetErrorMessage();
+      EXPECT_EQ(expected, result);
+    }
+  }
+
+  // An alternative to ExpandAndCheck, which uses the |templateBody| as the
+  // match pattern for the disassembled linked result.
+  void ExpandAndMatch(
+      const std::string& templateBody,
+      const spvtools::LinkerOptions options = spvtools::LinkerOptions()) {
+    spvtest::Binary linked_binary;
+    spv_result_t res = ExpandAndLink(templateBody, &linked_binary, options);
+    EXPECT_EQ(SPV_SUCCESS, res) << GetErrorMessage() << "\nExpanded from:\n"
+                                << templateBody;
+    if (res == SPV_SUCCESS) {
+      std::string result;
+      EXPECT_TRUE(
+          tools_.Disassemble(linked_binary, &result, disassemble_options_))
+          << GetErrorMessage();
+      auto match_res = effcee::Match(result, templateBody);
+      EXPECT_EQ(effcee::Result::Status::Ok, match_res.status())
+          << match_res.message() << "\nExpanded from:\n"
+          << templateBody << "\nChecking result:\n"
+          << result;
+    }
+  }
+
   // Links the given SPIR-V binaries together; SPV_ERROR_INVALID_POINTER is
   // returned if |linked_binary| is a null pointer.
   spv_result_t Link(
diff --git a/test/link/type_match_test.cpp b/test/link/type_match_test.cpp
new file mode 100644
index 0000000..291d13d
--- /dev/null
+++ b/test/link/type_match_test.cpp
@@ -0,0 +1,144 @@
+// Copyright (c) 2019 The Khronos Group Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "gmock/gmock.h"
+#include "test/link/linker_fixture.h"
+
+namespace spvtools {
+namespace {
+
+using TypeMatch = spvtest::LinkerTest;
+
+// Basic types
+#define PartInt(N) N " = OpTypeInt 32 0"
+#define PartFloat(N) N " = OpTypeFloat 32"
+#define PartOpaque(N) N " = OpTypeOpaque \"bar\""
+#define PartSampler(N) N " = OpTypeSampler"
+#define PartEvent(N) N " = OpTypeEvent"
+#define PartDeviceEvent(N) N " = OpTypeDeviceEvent"
+#define PartReserveId(N) N " = OpTypeReserveId"
+#define PartQueue(N) N " = OpTypeQueue"
+#define PartPipe(N) N " = OpTypePipe ReadWrite"
+#define PartPipeStorage(N) N " = OpTypePipeStorage"
+#define PartNamedBarrier(N) N " = OpTypeNamedBarrier"
+
+// Compound types
+#define PartVector(N, T) N " = OpTypeVector " T " 3"
+#define PartMatrix(N, T) N " = OpTypeMatrix " T " 4"
+#define PartImage(N, T) N " = OpTypeImage " T " 2D 0 0 0 0 Rgba32f"
+#define PartSampledImage(N, T) N " = OpTypeSampledImage " T
+#define PartArray(N, T) N " = OpTypeArray " T " %const"
+#define PartRuntimeArray(N, T) N " = OpTypeRuntimeArray " T
+#define PartStruct(N, T) N " = OpTypeStruct " T " " T
+#define PartPointer(N, T) N " = OpTypePointer Workgroup " T
+#define PartFunction(N, T) N " = OpTypeFunction " T " " T
+
+#define MatchPart1(F, N) \
+  "; CHECK: " Part##F("[[" #N ":%\\w+]]") "\n" Part##F("%" #N) "\n"
+#define MatchPart2(F, N, T)                                                 \
+  "; CHECK: " Part##F("[[" #N ":%\\w+]]", "[[" #T ":%\\w+]]") "\n" Part##F( \
+      "%" #N, "%" #T) "\n"
+
+#define MatchF(N, CODE)                                         \
+  TEST_F(TypeMatch, N) {                                        \
+    const std::string base =                                    \
+        "OpCapability Linkage\n"                                \
+        "OpCapability NamedBarrier\n"                           \
+        "OpCapability PipeStorage\n"                            \
+        "OpCapability Pipes\n"                                  \
+        "OpCapability DeviceEnqueue\n"                          \
+        "OpCapability Kernel\n"                                 \
+        "OpCapability Shader\n"                                 \
+        "OpCapability Addresses\n"                              \
+        "OpDecorate %var LinkageAttributes \"foo\" "            \
+        "{Import,Export}\n"                                     \
+        "; CHECK: [[baseint:%\\w+]] = OpTypeInt 32 1\n"         \
+        "%baseint = OpTypeInt 32 1\n"                           \
+        "; CHECK: [[const:%\\w+]] = OpConstant [[baseint]] 3\n" \
+        "%const = OpConstant %baseint 3\n" CODE                 \
+        "; CHECK: OpVariable [[type]] Uniform\n"                \
+        "%var = OpVariable %type Uniform";                      \
+    ExpandAndMatch(base);                                       \
+  }
+
+#define Match1(T) MatchF(Type##T, MatchPart1(T, type))
+#define Match2(T, A) \
+  MatchF(T##OfType##A, MatchPart1(A, a) MatchPart2(T, type, a))
+#define Match3(T, A, B)   \
+  MatchF(T##Of##A##Of##B, \
+         MatchPart1(B, b) MatchPart2(A, a, b) MatchPart2(T, type, a))
+
+// Basic types
+Match1(Int);
+Match1(Float);
+Match1(Opaque);
+Match1(Sampler);
+Match1(Event);
+Match1(DeviceEvent);
+Match1(ReserveId);
+Match1(Queue);
+Match1(Pipe);
+Match1(PipeStorage);
+Match1(NamedBarrier);
+
+// Simpler (restricted) compound types
+Match2(Vector, Float);
+Match3(Matrix, Vector, Float);
+Match2(Image, Float);
+
+// Unrestricted compound types
+// The following skip Array as it causes issues
+#define MatchCompounds1(A) \
+  Match2(RuntimeArray, A); \
+  Match2(Struct, A);       \
+  Match2(Pointer, A);      \
+  Match2(Function, A);     \
+// Match2(Array, A);  // Disabled as it fails currently
+#define MatchCompounds2(A, B) \
+  Match3(RuntimeArray, A, B); \
+  Match3(Struct, A, B);       \
+  Match3(Pointer, A, B);      \
+  Match3(Function, A, B);     \
+  // Match3(Array, A, B);  // Disabled as it fails currently
+
+MatchCompounds1(Float);
+// MatchCompounds2(Array, Float);
+MatchCompounds2(RuntimeArray, Float);
+MatchCompounds2(Struct, Float);
+MatchCompounds2(Pointer, Float);
+MatchCompounds2(Function, Float);
+
+// ForwardPointer tests, which don't fit into the previous mold
+#define MatchFpF(N, CODE)                                                    \
+  MatchF(N,                                                                  \
+         "; CHECK: [[type:%\\w+]] = OpTypeForwardPointer [[pointer:%\\w+]] " \
+         "Workgroup\n"                                                       \
+         "%type = OpTypeForwardPointer %pointer Workgroup\n" CODE            \
+         "; CHECK: [[pointer]] = OpTypePointer Workgroup [[realtype]]\n"     \
+         "%pointer = OpTypePointer Workgroup %realtype\n")
+#define MatchFp1(T) MatchFpF(ForwardPointerOf##T, MatchPart1(T, realtype))
+#define MatchFp2(T, A) \
+  MatchFpF(ForwardPointerOf##T, MatchPart1(A, a) MatchPart2(T, realtype, a))
+
+// Disabled currently, causes assertion failures
+/*
+MatchFp1(Float);
+MatchFp2(Array, Float);
+MatchFp2(RuntimeArray, Float);
+MatchFp2(Struct, Float);
+MatchFp2(Function, Float);
+// */
+
+}  // namespace
+}  // namespace spvtools