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.
This commit is contained in:
Jonathon Anderson 2019-05-24 15:40:28 -04:00 committed by Steven Perron
parent 4c73ebc46f
commit 3b5ab540ca
3 changed files with 242 additions and 0 deletions

View File

@ -23,5 +23,6 @@ add_spvtools_unittest(TARGET link
memory_model_test.cpp
partial_linkage_test.cpp
unique_ids_test.cpp
type_match_test.cpp
LIBS SPIRV-Tools-opt SPIRV-Tools-link
)

View File

@ -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 @@ class LinkerTest : public ::testing::Test {
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(

View File

@ -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