mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-26 01:31:06 +00:00
464111eaef
* Remove use of deprecated googletest macro INSTANTIATE_TEST_CASE_P has been deprecated. We need to use INSTANTIATE_TEST_SUITE_P instead. * Remove extra commas from test suites.
707 lines
25 KiB
C++
707 lines
25 KiB
C++
// Copyright (c) 2015-2016 The Khronos Group Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Validation tests for Logical Layout
|
|
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "gmock/gmock.h"
|
|
#include "source/diagnostic.h"
|
|
#include "test/unit_spirv.h"
|
|
#include "test/val/val_fixtures.h"
|
|
|
|
namespace spvtools {
|
|
namespace val {
|
|
namespace {
|
|
|
|
using ::testing::Eq;
|
|
using ::testing::HasSubstr;
|
|
using ::testing::StrEq;
|
|
|
|
using pred_type = std::function<spv_result_t(int)>;
|
|
using ValidateLayout = spvtest::ValidateBase<
|
|
std::tuple<int, std::tuple<std::string, pred_type, pred_type>>>;
|
|
|
|
// returns true if order is equal to VAL
|
|
template <int VAL, spv_result_t RET = SPV_ERROR_INVALID_LAYOUT>
|
|
spv_result_t Equals(int order) {
|
|
return order == VAL ? SPV_SUCCESS : RET;
|
|
}
|
|
|
|
// returns true if order is between MIN and MAX(inclusive)
|
|
template <int MIN, int MAX, spv_result_t RET = SPV_ERROR_INVALID_LAYOUT>
|
|
struct Range {
|
|
explicit Range(bool inverse = false) : inverse_(inverse) {}
|
|
spv_result_t operator()(int order) {
|
|
return (inverse_ ^ (order >= MIN && order <= MAX)) ? SPV_SUCCESS : RET;
|
|
}
|
|
|
|
private:
|
|
bool inverse_;
|
|
};
|
|
|
|
template <typename... T>
|
|
spv_result_t InvalidSet(int order) {
|
|
for (spv_result_t val : {T(true)(order)...})
|
|
if (val != SPV_SUCCESS) return val;
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
// SPIRV source used to test the logical layout
|
|
const std::vector<std::string>& getInstructions() {
|
|
// clang-format off
|
|
static const std::vector<std::string> instructions = {
|
|
"OpCapability Shader",
|
|
"OpExtension \"TestExtension\"",
|
|
"%inst = OpExtInstImport \"GLSL.std.450\"",
|
|
"OpMemoryModel Logical GLSL450",
|
|
"OpEntryPoint GLCompute %func \"\"",
|
|
"OpExecutionMode %func LocalSize 1 1 1",
|
|
"OpExecutionModeId %func LocalSizeId %one %one %one",
|
|
"%str = OpString \"Test String\"",
|
|
"%str2 = OpString \"blabla\"",
|
|
"OpSource GLSL 450 %str \"uniform vec3 var = vec3(4.0);\"",
|
|
"OpSourceContinued \"void main(){return;}\"",
|
|
"OpSourceExtension \"Test extension\"",
|
|
"OpName %func \"MyFunction\"",
|
|
"OpMemberName %struct 1 \"my_member\"",
|
|
"OpDecorate %dgrp RowMajor",
|
|
"OpMemberDecorate %struct 1 RowMajor",
|
|
"%dgrp = OpDecorationGroup",
|
|
"OpGroupDecorate %dgrp %mat33 %mat44",
|
|
"%intt = OpTypeInt 32 1",
|
|
"%floatt = OpTypeFloat 32",
|
|
"%voidt = OpTypeVoid",
|
|
"%boolt = OpTypeBool",
|
|
"%vec4 = OpTypeVector %floatt 4",
|
|
"%vec3 = OpTypeVector %floatt 3",
|
|
"%mat33 = OpTypeMatrix %vec3 3",
|
|
"%mat44 = OpTypeMatrix %vec4 4",
|
|
"%struct = OpTypeStruct %intt %mat33",
|
|
"%vfunct = OpTypeFunction %voidt",
|
|
"%viifunct = OpTypeFunction %voidt %intt %intt",
|
|
"%one = OpConstant %intt 1",
|
|
// TODO(umar): OpConstant fails because the type is not defined
|
|
// TODO(umar): OpGroupMemberDecorate
|
|
"OpLine %str 3 4",
|
|
"OpNoLine",
|
|
"%func = OpFunction %voidt None %vfunct",
|
|
"%l = OpLabel",
|
|
"OpReturn ; %func return",
|
|
"OpFunctionEnd ; %func end",
|
|
"%func2 = OpFunction %voidt None %viifunct",
|
|
"%funcp1 = OpFunctionParameter %intt",
|
|
"%funcp2 = OpFunctionParameter %intt",
|
|
"%fLabel = OpLabel",
|
|
"OpNop",
|
|
"OpReturn ; %func2 return",
|
|
"OpFunctionEnd"
|
|
};
|
|
return instructions;
|
|
}
|
|
|
|
static const int kRangeEnd = 1000;
|
|
pred_type All = Range<0, kRangeEnd>();
|
|
|
|
INSTANTIATE_TEST_SUITE_P(InstructionsOrder,
|
|
ValidateLayout,
|
|
::testing::Combine(::testing::Range((int)0, (int)getInstructions().size()),
|
|
// Note: Because of ID dependencies between instructions, some instructions
|
|
// are not free to be placed anywhere without triggering an non-layout
|
|
// validation error. Therefore, "Lines to compile" for some instructions
|
|
// are not "All" in the below.
|
|
//
|
|
// | Instruction | Line(s) valid | Lines to compile
|
|
::testing::Values(std::make_tuple(std::string("OpCapability") , Equals<0> , Range<0, 2>())
|
|
, std::make_tuple(std::string("OpExtension") , Equals<1> , All)
|
|
, std::make_tuple(std::string("OpExtInstImport") , Equals<2> , All)
|
|
, std::make_tuple(std::string("OpMemoryModel") , Equals<3> , Range<1, kRangeEnd>())
|
|
, std::make_tuple(std::string("OpEntryPoint") , Equals<4> , All)
|
|
, std::make_tuple(std::string("OpExecutionMode ") , Range<5, 6>() , All)
|
|
, std::make_tuple(std::string("OpExecutionModeId") , Range<5, 6>() , All)
|
|
, std::make_tuple(std::string("OpSource ") , Range<7, 11>() , Range<8, kRangeEnd>())
|
|
, std::make_tuple(std::string("OpSourceContinued ") , Range<7, 11>() , All)
|
|
, std::make_tuple(std::string("OpSourceExtension ") , Range<7, 11>() , All)
|
|
, std::make_tuple(std::string("%str2 = OpString ") , Range<7, 11>() , All)
|
|
, std::make_tuple(std::string("OpName ") , Range<12, 13>() , All)
|
|
, std::make_tuple(std::string("OpMemberName ") , Range<12, 13>() , All)
|
|
, std::make_tuple(std::string("OpDecorate ") , Range<14, 17>() , All)
|
|
, std::make_tuple(std::string("OpMemberDecorate ") , Range<14, 17>() , All)
|
|
, std::make_tuple(std::string("OpGroupDecorate ") , Range<14, 17>() , Range<17, kRangeEnd>())
|
|
, std::make_tuple(std::string("OpDecorationGroup") , Range<14, 17>() , Range<0, 16>())
|
|
, std::make_tuple(std::string("OpTypeBool") , Range<18, 31>() , All)
|
|
, std::make_tuple(std::string("OpTypeVoid") , Range<18, 31>() , Range<0, 26>())
|
|
, std::make_tuple(std::string("OpTypeFloat") , Range<18, 31>() , Range<0,21>())
|
|
, std::make_tuple(std::string("OpTypeInt") , Range<18, 31>() , Range<0, 21>())
|
|
, std::make_tuple(std::string("OpTypeVector %floatt 4") , Range<18, 31>() , Range<20, 24>())
|
|
, std::make_tuple(std::string("OpTypeMatrix %vec4 4") , Range<18, 31>() , Range<23, kRangeEnd>())
|
|
, std::make_tuple(std::string("OpTypeStruct") , Range<18, 31>() , Range<25, kRangeEnd>())
|
|
, std::make_tuple(std::string("%vfunct = OpTypeFunction"), Range<18, 31>() , Range<21, 31>())
|
|
, std::make_tuple(std::string("OpConstant") , Range<18, 31>() , Range<21, kRangeEnd>())
|
|
, std::make_tuple(std::string("OpLine ") , Range<18, kRangeEnd>() , Range<8, kRangeEnd>())
|
|
, std::make_tuple(std::string("OpNoLine") , Range<18, kRangeEnd>() , All)
|
|
, std::make_tuple(std::string("%fLabel = OpLabel") , Equals<39> , All)
|
|
, std::make_tuple(std::string("OpNop") , Equals<40> , Range<40,kRangeEnd>())
|
|
, std::make_tuple(std::string("OpReturn ; %func2 return") , Equals<41> , All)
|
|
)));
|
|
// clang-format on
|
|
|
|
// Creates a new vector which removes the string if the substr is found in the
|
|
// instructions vector and reinserts it in the location specified by order.
|
|
// NOTE: This will not work correctly if there are two instances of substr in
|
|
// instructions
|
|
std::vector<std::string> GenerateCode(std::string substr, int order) {
|
|
std::vector<std::string> code(getInstructions().size());
|
|
std::vector<std::string> inst(1);
|
|
partition_copy(std::begin(getInstructions()), std::end(getInstructions()),
|
|
std::begin(code), std::begin(inst),
|
|
[=](const std::string& str) {
|
|
return std::string::npos == str.find(substr);
|
|
});
|
|
|
|
code.insert(std::begin(code) + order, inst.front());
|
|
return code;
|
|
}
|
|
|
|
// This test will check the logical layout of a binary by removing each
|
|
// instruction in the pair of the INSTANTIATE_TEST_SUITE_P call and moving it in
|
|
// the SPIRV source formed by combining the vector "instructions".
|
|
TEST_P(ValidateLayout, Layout) {
|
|
int order;
|
|
std::string instruction;
|
|
pred_type pred;
|
|
pred_type test_pred; // Predicate to determine if the test should be build
|
|
std::tuple<std::string, pred_type, pred_type> testCase;
|
|
|
|
std::tie(order, testCase) = GetParam();
|
|
std::tie(instruction, pred, test_pred) = testCase;
|
|
|
|
// Skip test which break the code generation
|
|
if (test_pred(order)) return;
|
|
|
|
std::vector<std::string> code = GenerateCode(instruction, order);
|
|
|
|
std::stringstream ss;
|
|
std::copy(std::begin(code), std::end(code),
|
|
std::ostream_iterator<std::string>(ss, "\n"));
|
|
|
|
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
|
// printf("code: \n%s\n", ss.str().c_str());
|
|
CompileSuccessfully(ss.str(), env);
|
|
spv_result_t result;
|
|
// clang-format off
|
|
ASSERT_EQ(pred(order), result = ValidateInstructions(env))
|
|
<< "Actual: " << spvResultToString(result)
|
|
<< "\nExpected: " << spvResultToString(pred(order))
|
|
<< "\nOrder: " << order
|
|
<< "\nInstruction: " << instruction
|
|
<< "\nCode: \n" << ss.str();
|
|
// clang-format on
|
|
}
|
|
|
|
TEST_F(ValidateLayout, MemoryModelMissingBeforeEntryPoint) {
|
|
std::string str = R"(
|
|
OpCapability Matrix
|
|
OpExtension "TestExtension"
|
|
%inst = OpExtInstImport "GLSL.std.450"
|
|
OpEntryPoint GLCompute %func ""
|
|
OpExecutionMode %func LocalSize 1 1 1
|
|
)";
|
|
|
|
CompileSuccessfully(str);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
|
|
EXPECT_THAT(
|
|
getDiagnosticString(),
|
|
HasSubstr(
|
|
"EntryPoint cannot appear before the memory model instruction"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, MemoryModelMissing) {
|
|
char str[] = R"(OpCapability Linkage)";
|
|
CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
|
|
ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("Missing required OpMemoryModel instruction"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, MemoryModelSpecifiedTwice) {
|
|
char str[] = R"(
|
|
OpCapability Linkage
|
|
OpCapability Shader
|
|
OpMemoryModel Logical Simple
|
|
OpMemoryModel Logical Simple
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
|
|
ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("OpMemoryModel should only be provided once"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, FunctionDefinitionBeforeDeclarationBad) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpMemoryModel Logical GLSL450
|
|
OpDecorate %var Restrict
|
|
%intt = OpTypeInt 32 1
|
|
%voidt = OpTypeVoid
|
|
%vfunct = OpTypeFunction %voidt
|
|
%vifunct = OpTypeFunction %voidt %intt
|
|
%ptrt = OpTypePointer Function %intt
|
|
%func = OpFunction %voidt None %vfunct
|
|
%funcl = OpLabel
|
|
OpNop
|
|
OpReturn
|
|
OpFunctionEnd
|
|
%func2 = OpFunction %voidt None %vifunct ; must appear before definition
|
|
%func2p = OpFunctionParameter %intt
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
|
|
EXPECT_THAT(
|
|
getDiagnosticString(),
|
|
HasSubstr(
|
|
"Function declarations must appear before function definitions."));
|
|
}
|
|
|
|
// TODO(umar): Passes but gives incorrect error message. Should be fixed after
|
|
// type checking
|
|
TEST_F(ValidateLayout, LabelBeforeFunctionParameterBad) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpMemoryModel Logical GLSL450
|
|
OpDecorate %var Restrict
|
|
%intt = OpTypeInt 32 1
|
|
%voidt = OpTypeVoid
|
|
%vfunct = OpTypeFunction %voidt
|
|
%vifunct = OpTypeFunction %voidt %intt
|
|
%ptrt = OpTypePointer Function %intt
|
|
%func = OpFunction %voidt None %vifunct
|
|
%funcl = OpLabel ; Label appears before function parameter
|
|
%func2p = OpFunctionParameter %intt
|
|
OpNop
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("Function parameters must only appear immediately "
|
|
"after the function definition"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, FuncParameterNotImmediatlyAfterFuncBad) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpMemoryModel Logical GLSL450
|
|
OpDecorate %var Restrict
|
|
%intt = OpTypeInt 32 1
|
|
%voidt = OpTypeVoid
|
|
%vfunct = OpTypeFunction %voidt
|
|
%vifunct = OpTypeFunction %voidt %intt
|
|
%ptrt = OpTypePointer Function %intt
|
|
%func = OpFunction %voidt None %vifunct
|
|
%funcl = OpLabel
|
|
OpNop
|
|
OpBranch %next
|
|
%func2p = OpFunctionParameter %intt ;FunctionParameter appears in a function but not immediately afterwards
|
|
%next = OpLabel
|
|
OpNop
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("Function parameters must only appear immediately "
|
|
"after the function definition"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, OpUndefCanAppearInTypeDeclarationSection) {
|
|
std::string str = R"(
|
|
OpCapability Kernel
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical OpenCL
|
|
%voidt = OpTypeVoid
|
|
%uintt = OpTypeInt 32 0
|
|
%funct = OpTypeFunction %voidt
|
|
%udef = OpUndef %uintt
|
|
%func = OpFunction %voidt None %funct
|
|
%entry = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str);
|
|
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
|
|
}
|
|
|
|
TEST_F(ValidateLayout, OpUndefCanAppearInBlock) {
|
|
std::string str = R"(
|
|
OpCapability Kernel
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical OpenCL
|
|
%voidt = OpTypeVoid
|
|
%uintt = OpTypeInt 32 0
|
|
%funct = OpTypeFunction %voidt
|
|
%func = OpFunction %voidt None %funct
|
|
%entry = OpLabel
|
|
%udef = OpUndef %uintt
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str);
|
|
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
|
|
}
|
|
|
|
TEST_F(ValidateLayout, MissingFunctionEndForFunctionWithBody) {
|
|
const auto s = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
%void = OpTypeVoid
|
|
%tf = OpTypeFunction %void
|
|
%f = OpFunction %void None %tf
|
|
%l = OpLabel
|
|
OpReturn
|
|
)";
|
|
|
|
CompileSuccessfully(s);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
StrEq("Missing OpFunctionEnd at end of module."));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, MissingFunctionEndForFunctionPrototype) {
|
|
const auto s = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
%void = OpTypeVoid
|
|
%tf = OpTypeFunction %void
|
|
%f = OpFunction %void None %tf
|
|
)";
|
|
|
|
CompileSuccessfully(s);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
StrEq("Missing OpFunctionEnd at end of module."));
|
|
}
|
|
|
|
using ValidateOpFunctionParameter = spvtest::ValidateBase<int>;
|
|
|
|
TEST_F(ValidateOpFunctionParameter, OpLineBetweenParameters) {
|
|
const auto s = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
%foo_frag = OpString "foo.frag"
|
|
%i32 = OpTypeInt 32 1
|
|
%tf = OpTypeFunction %i32 %i32 %i32
|
|
%c = OpConstant %i32 123
|
|
%f = OpFunction %i32 None %tf
|
|
OpLine %foo_frag 1 1
|
|
%p1 = OpFunctionParameter %i32
|
|
OpNoLine
|
|
%p2 = OpFunctionParameter %i32
|
|
%l = OpLabel
|
|
OpReturnValue %c
|
|
OpFunctionEnd
|
|
)";
|
|
CompileSuccessfully(s);
|
|
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
|
|
}
|
|
|
|
TEST_F(ValidateOpFunctionParameter, TooManyParameters) {
|
|
const auto s = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
%i32 = OpTypeInt 32 1
|
|
%tf = OpTypeFunction %i32 %i32 %i32
|
|
%c = OpConstant %i32 123
|
|
%f = OpFunction %i32 None %tf
|
|
%p1 = OpFunctionParameter %i32
|
|
%p2 = OpFunctionParameter %i32
|
|
%xp3 = OpFunctionParameter %i32
|
|
%xp4 = OpFunctionParameter %i32
|
|
%xp5 = OpFunctionParameter %i32
|
|
%xp6 = OpFunctionParameter %i32
|
|
%xp7 = OpFunctionParameter %i32
|
|
%l = OpLabel
|
|
OpReturnValue %c
|
|
OpFunctionEnd
|
|
)";
|
|
CompileSuccessfully(s);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
|
|
}
|
|
|
|
using ValidateEntryPoint = spvtest::ValidateBase<bool>;
|
|
|
|
// Tests that not having OpEntryPoint causes an error.
|
|
TEST_F(ValidateEntryPoint, NoEntryPointBad) {
|
|
std::string spirv = R"(
|
|
OpCapability Shader
|
|
OpMemoryModel Logical GLSL450)";
|
|
CompileSuccessfully(spirv);
|
|
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("No OpEntryPoint instruction was found. This is only "
|
|
"allowed if the Linkage capability is being used."));
|
|
}
|
|
|
|
// Invalid. A function may not be a target of both OpEntryPoint and
|
|
// OpFunctionCall.
|
|
TEST_F(ValidateEntryPoint, FunctionIsTargetOfEntryPointAndFunctionCallBad) {
|
|
std::string spirv = R"(
|
|
OpCapability Shader
|
|
OpMemoryModel Logical GLSL450
|
|
OpEntryPoint Fragment %foo "foo"
|
|
OpExecutionMode %foo OriginUpperLeft
|
|
%voidt = OpTypeVoid
|
|
%funct = OpTypeFunction %voidt
|
|
%foo = OpFunction %voidt None %funct
|
|
%entry = OpLabel
|
|
%recurse = OpFunctionCall %voidt %foo
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
CompileSuccessfully(spirv);
|
|
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateInstructions());
|
|
EXPECT_THAT(
|
|
getDiagnosticString(),
|
|
HasSubstr("A function (1) may not be targeted by both an OpEntryPoint "
|
|
"instruction and an OpFunctionCall instruction."));
|
|
}
|
|
|
|
// Invalid. Must be within a function to make a function call.
|
|
TEST_F(ValidateEntryPoint, FunctionCallOutsideFunctionBody) {
|
|
std::string spirv = R"(
|
|
OpCapability Shader
|
|
%1 = OpExtInstImport "GLSL.std.450"
|
|
OpMemoryModel Logical GLSL450
|
|
OpName %variableName "variableName"
|
|
%34 = OpFunctionCall %variableName %1
|
|
)";
|
|
CompileSuccessfully(spirv);
|
|
EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("FunctionCall must happen within a function body."));
|
|
}
|
|
|
|
// Valid. Module with a function but no entry point is valid when Linkage
|
|
// Capability is used.
|
|
TEST_F(ValidateEntryPoint, NoEntryPointWithLinkageCapGood) {
|
|
std::string spirv = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
%voidt = OpTypeVoid
|
|
%funct = OpTypeFunction %voidt
|
|
%foo = OpFunction %voidt None %funct
|
|
%entry = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
CompileSuccessfully(spirv);
|
|
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
|
|
}
|
|
|
|
TEST_F(ValidateLayout, ModuleProcessedInvalidIn10) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
OpName %void "void"
|
|
OpModuleProcessed "this is ok in 1.1 and later"
|
|
OpDecorate %void Volatile ; bogus, but makes the example short
|
|
%void = OpTypeVoid
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
|
|
ASSERT_EQ(SPV_ERROR_WRONG_VERSION,
|
|
ValidateInstructions(SPV_ENV_UNIVERSAL_1_0));
|
|
// In a 1.0 environment the version check fails.
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("Invalid SPIR-V binary version 1.1 for target "
|
|
"environment SPIR-V 1.0."));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, ModuleProcessedValidIn11) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
OpName %void "void"
|
|
OpModuleProcessed "this is ok in 1.1 and later"
|
|
OpDecorate %void Volatile ; bogus, but makes the example short
|
|
%void = OpTypeVoid
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
|
|
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
|
|
EXPECT_THAT(getDiagnosticString(), Eq(""));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, ModuleProcessedBeforeLastNameIsTooEarly) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
OpModuleProcessed "this is too early"
|
|
OpName %void "void"
|
|
%void = OpTypeVoid
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
|
|
ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
|
|
// By the mechanics of the validator, we assume ModuleProcessed is in the
|
|
// right spot, but then that OpName is in the wrong spot.
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("Name cannot appear in a function declaration"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, ModuleProcessedInvalidAfterFirstAnnotation) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpCapability Linkage
|
|
OpMemoryModel Logical GLSL450
|
|
OpDecorate %void Volatile ; this is bogus, but keeps the example short
|
|
OpModuleProcessed "this is too late"
|
|
%void = OpTypeVoid
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
|
|
ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
|
|
EXPECT_THAT(
|
|
getDiagnosticString(),
|
|
HasSubstr("ModuleProcessed cannot appear in a function declaration"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, ModuleProcessedInvalidInFunctionBeforeLabel) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpMemoryModel Logical GLSL450
|
|
OpEntryPoint GLCompute %main "main"
|
|
%void = OpTypeVoid
|
|
%voidfn = OpTypeFunction %void
|
|
%main = OpFunction %void None %voidfn
|
|
OpModuleProcessed "this is too late, in function before label"
|
|
%entry = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
|
|
ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
|
|
EXPECT_THAT(
|
|
getDiagnosticString(),
|
|
HasSubstr("ModuleProcessed cannot appear in a function declaration"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, ModuleProcessedInvalidInBasicBlock) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpMemoryModel Logical GLSL450
|
|
OpEntryPoint GLCompute %main "main"
|
|
%void = OpTypeVoid
|
|
%voidfn = OpTypeFunction %void
|
|
%main = OpFunction %void None %voidfn
|
|
%entry = OpLabel
|
|
OpModuleProcessed "this is too late, in basic block"
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_UNIVERSAL_1_1);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT,
|
|
ValidateInstructions(SPV_ENV_UNIVERSAL_1_1));
|
|
EXPECT_THAT(
|
|
getDiagnosticString(),
|
|
HasSubstr("ModuleProcessed cannot appear in a function declaration"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, WebGPUCallerBeforeCalleeBad) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpCapability VulkanMemoryModelKHR
|
|
OpExtension "SPV_KHR_vulkan_memory_model"
|
|
OpMemoryModel Logical VulkanKHR
|
|
OpEntryPoint GLCompute %main "main"
|
|
%void = OpTypeVoid
|
|
%voidfn = OpTypeFunction %void
|
|
%main = OpFunction %void None %voidfn
|
|
%1 = OpLabel
|
|
%2 = OpFunctionCall %void %callee
|
|
OpReturn
|
|
OpFunctionEnd
|
|
%callee = OpFunction %void None %voidfn
|
|
%3 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
|
|
ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
|
EXPECT_THAT(getDiagnosticString(),
|
|
HasSubstr("For WebGPU, functions need to be defined before being "
|
|
"called.\n %5 = OpFunctionCall %void %6\n"));
|
|
}
|
|
|
|
TEST_F(ValidateLayout, WebGPUCalleeBeforeCallerGood) {
|
|
char str[] = R"(
|
|
OpCapability Shader
|
|
OpCapability VulkanMemoryModelKHR
|
|
OpExtension "SPV_KHR_vulkan_memory_model"
|
|
OpMemoryModel Logical VulkanKHR
|
|
OpEntryPoint GLCompute %main "main"
|
|
%void = OpTypeVoid
|
|
%voidfn = OpTypeFunction %void
|
|
%callee = OpFunction %void None %voidfn
|
|
%3 = OpLabel
|
|
OpReturn
|
|
OpFunctionEnd
|
|
%main = OpFunction %void None %voidfn
|
|
%1 = OpLabel
|
|
%2 = OpFunctionCall %void %callee
|
|
OpReturn
|
|
OpFunctionEnd
|
|
)";
|
|
|
|
CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
|
|
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
|
|
}
|
|
|
|
// TODO(umar): Test optional instructions
|
|
|
|
} // namespace
|
|
} // namespace val
|
|
} // namespace spvtools
|