Add support for SPV_KHR_non_semantic_info (#3110)

Add support for SPV_KHR_non_semantic_info

This entails a couple of changes:

- Allowing unknown OpExtInstImport that begin with the prefix `NonSemantic.`
- Allowing OpExtInst that reference any of those sets to contain unknown
  ext inst instruction numbers, and assume the format is always a series of IDs
  as guaranteed by the extension.
- Allowing those OpExtInst to appear in the types/variables/constants section.
- Not stripping OpString in the --strip-debug pass, since it may be referenced
  by these non-semantic OpExtInsts.
- Stripping them instead in the --strip-reflect pass.

* Add adjacency validation of non-semantic OpExtInst

- We validate and test that OpExtInst cannot appear before or between
  OpPhi instructions, or before/between OpFunctionParameter
  instructions.

* Change non-semantic extinst type to single value

* Add helper function spvExtInstIsNonSemantic() which will check if the extinst
  set is non-semantic or not, either the unknown generic value or any future
  recognised non-semantic set.

* Add test of a complex non-semantic extinst

* Use DefUseManager in StripDebugInfoPass to strip some OpStrings

* Any OpString used by a non-semantic instruction cannot be stripped, all others
  can so we search for uses to see if each string can be removed.
* We only do this if the non-semantic debug info extension is enabled, otherwise
  all strings can be trivially removed.

* Silence -Winconsistent-missing-override in protobufs
This commit is contained in:
David Neto 2019-12-18 18:10:29 -05:00 committed by GitHub
parent 38d7fbaad0
commit e70b009b0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 841 additions and 26 deletions

View File

@ -108,6 +108,9 @@ if(SPIRV_BUILD_FUZZER)
set(protobuf_BUILD_TESTS OFF CACHE BOOL "Disable protobuf tests")
set(protobuf_MSVC_STATIC_RUNTIME OFF CACHE BOOL "Do not build protobuf static runtime")
if (IS_DIRECTORY ${PROTOBUF_DIR})
if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
add_definitions(-Wno-inconsistent-missing-override)
endif()
add_subdirectory(${PROTOBUF_DIR} EXCLUDE_FROM_ALL)
else()
message(FATAL_ERROR

View File

@ -249,6 +249,11 @@ typedef enum spv_ext_inst_type_t {
SPV_EXT_INST_TYPE_SPV_AMD_SHADER_BALLOT,
SPV_EXT_INST_TYPE_DEBUGINFO,
// Multiple distinct extended instruction set types could return this
// value, if they are prefixed with NonSemantic. and are otherwise
// unrecognised
SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN,
SPV_FORCE_32_BIT_ENUM(spv_ext_inst_type_t)
} spv_ext_inst_type_t;

View File

@ -477,9 +477,22 @@ spv_result_t Parser::parseOperand(size_t inst_offset,
assert(SpvOpExtInst == opcode);
assert(inst->ext_inst_type != SPV_EXT_INST_TYPE_NONE);
spv_ext_inst_desc ext_inst;
if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst))
return diagnostic() << "Invalid extended instruction number: " << word;
spvPushOperandTypes(ext_inst->operandTypes, expected_operands);
if (grammar_.lookupExtInst(inst->ext_inst_type, word, &ext_inst) ==
SPV_SUCCESS) {
// if we know about this ext inst, push the expected operands
spvPushOperandTypes(ext_inst->operandTypes, expected_operands);
} else {
// if we don't know this extended instruction and the set isn't
// non-semantic, we cannot process further
if (!spvExtInstIsNonSemantic(inst->ext_inst_type)) {
return diagnostic()
<< "Invalid extended instruction number: " << word;
} else {
// for non-semantic instruction sets, we know the form of all such
// extended instructions contains a series of IDs as parameters
expected_operands->push_back(SPV_OPERAND_TYPE_VARIABLE_ID);
}
}
} break;
case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {

View File

@ -217,10 +217,18 @@ void Disassembler::EmitOperand(const spv_parsed_instruction_t& inst,
break;
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER: {
spv_ext_inst_desc ext_inst;
if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst))
assert(false && "should have caught this earlier");
SetRed();
stream_ << ext_inst->name;
if (grammar_.lookupExtInst(inst.ext_inst_type, word, &ext_inst) ==
SPV_SUCCESS) {
stream_ << ext_inst->name;
} else {
if (!spvExtInstIsNonSemantic(inst.ext_inst_type)) {
assert(false && "should have caught this earlier");
} else {
// for non-semantic instruction sets we can just print the number
stream_ << word;
}
}
} break;
case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {
spv_opcode_desc opcode_desc;

View File

@ -116,9 +116,21 @@ spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name) {
if (!strcmp("DebugInfo", name)) {
return SPV_EXT_INST_TYPE_DEBUGINFO;
}
// ensure to add any known non-semantic extended instruction sets
// above this point, and update spvExtInstIsNonSemantic()
if (!strncmp("NonSemantic.", name, 12)) {
return SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN;
}
return SPV_EXT_INST_TYPE_NONE;
}
bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type) {
if (type == SPV_EXT_INST_TYPE_NONSEMANTIC_UNKNOWN) {
return true;
}
return false;
}
spv_result_t spvExtInstTableNameLookup(const spv_ext_inst_table table,
const spv_ext_inst_type_t type,
const char* name,

View File

@ -21,6 +21,9 @@
// Gets the type of the extended instruction set with the specified name.
spv_ext_inst_type_t spvExtInstImportTypeGet(const char* name);
// Returns true if the extended instruction set is non-semantic
bool spvExtInstIsNonSemantic(const spv_ext_inst_type_t type);
// Finds the named extented instruction of the given type in the given extended
// instruction table. On success, returns SPV_SUCCESS and writes a handle of
// the instruction entry into *entry.

View File

@ -16,6 +16,7 @@
#include <utility>
#include "source/ext_inst.h"
#include "source/opt/log.h"
#include "source/opt/reflect.h"
#include "source/util/make_unique.h"
@ -113,11 +114,14 @@ bool IrLoader::AddInstruction(const spv_parsed_instruction_t* inst) {
} else if (IsTypeInst(opcode)) {
module_->AddType(std::move(spv_inst));
} else if (IsConstantInst(opcode) || opcode == SpvOpVariable ||
opcode == SpvOpUndef) {
opcode == SpvOpUndef ||
(opcode == SpvOpExtInst &&
spvExtInstIsNonSemantic(inst->ext_inst_type))) {
module_->AddGlobalValue(std::move(spv_inst));
} else {
Errorf(consumer_, src, loc,
"Unhandled inst type (opcode: %d) found outside function definition.",
"Unhandled inst type (opcode: %d) found outside function "
"definition.",
opcode);
return false;
}

View File

@ -19,12 +19,60 @@ namespace spvtools {
namespace opt {
Pass::Status StripDebugInfoPass::Process() {
bool modified = !context()->debugs1().empty() ||
!context()->debugs2().empty() ||
!context()->debugs3().empty();
bool uses_non_semantic_info = false;
for (auto& inst : context()->module()->extensions()) {
const char* ext_name =
reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
if (0 == std::strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
uses_non_semantic_info = true;
}
}
std::vector<Instruction*> to_kill;
for (auto& dbg : context()->debugs1()) to_kill.push_back(&dbg);
// if we use non-semantic info, it may reference OpString. Do a more
// expensive pass checking the uses of the OpString to see if any are
// OpExtInst on a non-semantic instruction set. If we're not using the
// extension then we can do a simpler pass and kill all debug1 instructions
if (uses_non_semantic_info) {
for (auto& inst : context()->module()->debugs1()) {
switch (inst.opcode()) {
case SpvOpString: {
analysis::DefUseManager* def_use = context()->get_def_use_mgr();
// see if this string is used anywhere by a non-semantic instruction
bool no_nonsemantic_use =
def_use->WhileEachUser(&inst, [def_use](Instruction* use) {
if (use->opcode() == SpvOpExtInst) {
auto ext_inst_set =
def_use->GetDef(use->GetSingleWordInOperand(0u));
const char* extension_name = reinterpret_cast<const char*>(
&ext_inst_set->GetInOperand(0).words[0]);
if (0 == std::strncmp(extension_name, "NonSemantic.", 12)) {
// found a non-semantic use, return false as we cannot
// remove this OpString
return false;
}
}
// other instructions can't be a non-semantic use
return true;
});
if (no_nonsemantic_use) to_kill.push_back(&inst);
break;
}
default:
to_kill.push_back(&inst);
break;
}
}
} else {
for (auto& dbg : context()->debugs1()) to_kill.push_back(&dbg);
}
for (auto& dbg : context()->debugs2()) to_kill.push_back(&dbg);
for (auto& dbg : context()->debugs3()) to_kill.push_back(&dbg);
@ -38,8 +86,11 @@ Pass::Status StripDebugInfoPass::Process() {
return false;
});
bool modified = !to_kill.empty();
for (auto* inst : to_kill) context()->KillInst(inst);
// clear OpLine information
context()->module()->ForEachInst([&modified](Instruction* inst) {
modified |= !inst->dbg_line_insts().empty();
inst->dbg_line_insts().clear();

View File

@ -67,9 +67,54 @@ Pass::Status StripReflectInfoPass::Process() {
} else if (!other_uses_for_decorate_string &&
0 == std::strcmp(ext_name, "SPV_GOOGLE_decorate_string")) {
to_remove.push_back(&inst);
} else if (0 == std::strcmp(ext_name, "SPV_KHR_non_semantic_info")) {
to_remove.push_back(&inst);
}
}
// clear all debug data now if it hasn't been cleared already, to remove any
// remaining OpString that may have been referenced by non-semantic extinsts
for (auto& dbg : context()->debugs1()) to_remove.push_back(&dbg);
for (auto& dbg : context()->debugs2()) to_remove.push_back(&dbg);
for (auto& dbg : context()->debugs3()) to_remove.push_back(&dbg);
// remove any extended inst imports that are non semantic
std::unordered_set<uint32_t> non_semantic_sets;
for (auto& inst : context()->module()->ext_inst_imports()) {
assert(inst.opcode() == SpvOpExtInstImport &&
"Expecting an import of an extension's instruction set.");
const char* extension_name =
reinterpret_cast<const char*>(&inst.GetInOperand(0).words[0]);
if (0 == std::strncmp(extension_name, "NonSemantic.", 12)) {
non_semantic_sets.insert(inst.result_id());
to_remove.push_back(&inst);
}
}
// if we removed some non-semantic sets, then iterate over the instructions in
// the module to remove any OpExtInst that referenced those sets
if (!non_semantic_sets.empty()) {
context()->module()->ForEachInst(
[&non_semantic_sets, &to_remove](Instruction* inst) {
if (inst->opcode() == SpvOpExtInst) {
if (non_semantic_sets.find(inst->GetSingleWordInOperand(0)) !=
non_semantic_sets.end()) {
to_remove.push_back(inst);
}
}
});
}
// OpName must come first, since they may refer to other debug instructions.
// If they are after the instructions that refer to, then they will be killed
// when that instruction is killed, which will lead to a double kill.
std::sort(to_remove.begin(), to_remove.end(),
[](Instruction* lhs, Instruction* rhs) -> bool {
if (lhs->opcode() == SpvOpName && rhs->opcode() != SpvOpName)
return true;
return false;
});
for (auto* inst : to_remove) {
modified = true;
context()->KillInst(inst);

View File

@ -242,14 +242,37 @@ spv_result_t spvTextEncodeOperand(const spvtools::AssemblyGrammar& grammar,
// The assembler accepts the symbolic name for an extended instruction,
// and emits its corresponding number.
spv_ext_inst_desc extInst;
if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst)) {
return context->diagnostic()
<< "Invalid extended instruction name '" << textValue << "'.";
}
spvInstructionAddWord(pInst, extInst->ext_inst);
if (grammar.lookupExtInst(pInst->extInstType, textValue, &extInst) ==
SPV_SUCCESS) {
// if we know about this extended instruction, push the numeric value
spvInstructionAddWord(pInst, extInst->ext_inst);
// Prepare to parse the operands for the extended instructions.
spvPushOperandTypes(extInst->operandTypes, pExpectedOperands);
// Prepare to parse the operands for the extended instructions.
spvPushOperandTypes(extInst->operandTypes, pExpectedOperands);
} else {
// if we don't know this extended instruction and the set isn't
// non-semantic, we cannot process further
if (!spvExtInstIsNonSemantic(pInst->extInstType)) {
return context->diagnostic()
<< "Invalid extended instruction name '" << textValue << "'.";
} else {
// for non-semantic instruction sets, as long as the text name is an
// integer value we can encode it since we know the form of all such
// extended instructions
spv_literal_t extInstValue;
if (spvTextToLiteral(textValue, &extInstValue) ||
extInstValue.type != SPV_LITERAL_TYPE_UINT_32) {
return context->diagnostic()
<< "Couldn't translate unknown extended instruction name '"
<< textValue << "' to unsigned integer.";
}
spvInstructionAddWord(pInst, extInstValue.value.u32);
// opcode contains an unknown number of IDs.
pExpectedOperands->push_back(SPV_OPERAND_TYPE_VARIABLE_ID);
}
}
} break;
case SPV_OPERAND_TYPE_SPEC_CONSTANT_OP_NUMBER: {

View File

@ -21,6 +21,7 @@
#include <utility>
#include <vector>
#include "source/ext_inst.h"
#include "source/table.h"
#include "spirv-tools/libspirv.h"
@ -85,6 +86,11 @@ class Instruction {
return inst_.ext_inst_type;
}
bool IsNonSemantic() const {
return opcode() == SpvOp::SpvOpExtInst &&
spvExtInstIsNonSemantic(inst_.ext_inst_type);
}
// Casts the words belonging to the operand under |index| to |T| and returns.
template <typename T>
T GetOperandAs(size_t index) const {

View File

@ -286,7 +286,8 @@ spv_result_t ValidateDecorationGroup(ValidationState_t& _,
auto use = pair.first;
if (use->opcode() != SpvOpDecorate && use->opcode() != SpvOpGroupDecorate &&
use->opcode() != SpvOpGroupMemberDecorate &&
use->opcode() != SpvOpName && use->opcode() != SpvOpDecorateId) {
use->opcode() != SpvOpName && use->opcode() != SpvOpDecorateId &&
!use->IsNonSemantic()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Result id of OpDecorationGroup can only "
<< "be targeted by OpName, OpGroupDecorate, "

View File

@ -1275,6 +1275,7 @@ spv_result_t CheckFPRoundingModeForShaders(ValidationState_t& vstate,
const auto store = use.first;
if (store->opcode() == SpvOpFConvert) continue;
if (spvOpcodeIsDebug(store->opcode())) continue;
if (store->IsNonSemantic()) continue;
if (spvOpcodeIsDecoration(store->opcode())) continue;
if (store->opcode() != SpvOpStore) {
return vstate.diag(SPV_ERROR_INVALID_ID, &inst)

View File

@ -61,8 +61,8 @@ spv_result_t ValidateExtension(ValidationState_t& _, const Instruction* inst) {
spv_result_t ValidateExtInstImport(ValidationState_t& _,
const Instruction* inst) {
const auto name_id = 1;
if (spvIsWebGPUEnv(_.context()->target_env)) {
const auto name_id = 1;
const std::string name(reinterpret_cast<const char*>(
inst->words().data() + inst->operands()[name_id].offset));
if (name != "GLSL.std.450") {
@ -72,6 +72,16 @@ spv_result_t ValidateExtInstImport(ValidationState_t& _,
}
}
if (!_.HasExtension(kSPV_KHR_non_semantic_info)) {
const std::string name(reinterpret_cast<const char*>(
inst->words().data() + inst->operands()[name_id].offset));
if (name.find("NonSemantic.") == 0) {
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "NonSemantic extended instruction sets cannot be declared "
"without SPV_KHR_non_semantic_info.";
}
}
return SPV_SUCCESS;
}

View File

@ -87,7 +87,8 @@ spv_result_t ValidateFunction(ValidationState_t& _, const Instruction* inst) {
for (auto& pair : inst->uses()) {
const auto* use = pair.first;
if (std::find(acceptable.begin(), acceptable.end(), use->opcode()) ==
acceptable.end()) {
acceptable.end() &&
!use->IsNonSemantic()) {
return _.diag(SPV_ERROR_INVALID_ID, use)
<< "Invalid use of function result id " << _.getIdName(inst->id())
<< ".";

View File

@ -167,7 +167,8 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
const auto opcode = inst->opcode();
if (spvOpcodeGeneratesType(def->opcode()) &&
!spvOpcodeGeneratesType(opcode) && !spvOpcodeIsDebug(opcode) &&
!spvOpcodeIsDecoration(opcode) && opcode != SpvOpFunction &&
!inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) &&
opcode != SpvOpFunction &&
opcode != SpvOpCooperativeMatrixLengthNV &&
!(opcode == SpvOpSpecConstantOp &&
inst->word(3) == SpvOpCooperativeMatrixLengthNV)) {
@ -175,7 +176,7 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
<< "Operand " << _.getIdName(operand_word)
<< " cannot be a type";
} else if (def->type_id() == 0 && !spvOpcodeGeneratesType(opcode) &&
!spvOpcodeIsDebug(opcode) &&
!spvOpcodeIsDebug(opcode) && !inst->IsNonSemantic() &&
!spvOpcodeIsDecoration(opcode) &&
!spvOpcodeIsBranch(opcode) && opcode != SpvOpPhi &&
opcode != SpvOpExtInst && opcode != SpvOpExtInstImport &&
@ -187,6 +188,11 @@ spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Operand " << _.getIdName(operand_word)
<< " requires a type";
} else if (def->IsNonSemantic() && !inst->IsNonSemantic()) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Operand " << _.getIdName(operand_word)
<< " in semantic instruction cannot be a non-semantic "
"instruction";
} else {
ret = SPV_SUCCESS;
}

View File

@ -34,6 +34,30 @@ namespace {
// checked.
spv_result_t ModuleScopedInstructions(ValidationState_t& _,
const Instruction* inst, SpvOp opcode) {
switch (opcode) {
case SpvOpExtInst:
if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
// non-semantic extinst opcodes are allowed beginning in the types
// section, but since they must name a return type they cannot be the
// first instruction in the types section. Therefore check that we are
// already in it.
if (_.current_layout_section() < kLayoutTypes) {
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
<< "Non-semantic OpExtInst must not appear before types "
<< "section";
}
} else {
// otherwise they must be used in a block
if (_.current_layout_section() < kLayoutFunctionDefinitions) {
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
<< spvOpcodeString(opcode) << " must appear in a block";
}
}
break;
default:
break;
}
while (_.IsOpcodeInCurrentLayoutSection(opcode) == false) {
_.ProgressToNextLayoutSectionOrder();
@ -144,6 +168,29 @@ spv_result_t FunctionScopedInstructions(ValidationState_t& _,
}
break;
case SpvOpExtInst:
if (spvExtInstIsNonSemantic(inst->ext_inst_type())) {
// non-semantic extinst opcodes are allowed beginning in the types
// section, but must either be placed outside a function declaration,
// or inside a block.
if (_.current_layout_section() < kLayoutTypes) {
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
<< "Non-semantic OpExtInst must not appear before types "
<< "section";
} else if (_.in_function_body() && _.in_block() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
<< "Non-semantic OpExtInst within function definition must "
"appear in a block";
}
} else {
// otherwise they must be used in a block
if (_.in_block() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
<< spvOpcodeString(opcode) << " must appear in a block";
}
}
break;
default:
if (_.current_layout_section() == kLayoutFunctionDeclarations &&
_.in_function_body()) {

View File

@ -536,7 +536,7 @@ spv_result_t ValidateTypeFunction(ValidationState_t& _,
for (auto& pair : inst->uses()) {
const auto* use = pair.first;
if (use->opcode() != SpvOpFunction && !spvOpcodeIsDebug(use->opcode()) &&
!spvOpcodeIsDecoration(use->opcode())) {
!use->IsNonSemantic() && !spvOpcodeIsDecoration(use->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, use)
<< "Invalid use of function type result id "
<< _.getIdName(inst->id()) << ".";

View File

@ -93,6 +93,9 @@ bool IsInstructionInLayoutSection(ModuleLayoutSection layout, SpvOp op) {
case SpvOpLine:
case SpvOpNoLine:
case SpvOpUndef:
// SpvOpExtInst is only allowed here for certain extended instruction
// sets. This will be checked separately
case SpvOpExtInst:
out = true;
break;
default: break;

View File

@ -102,6 +102,7 @@ set(TEST_SOURCES
enum_set_test.cpp
ext_inst.debuginfo_test.cpp
ext_inst.glsl_test.cpp
ext_inst.non_semantic_test.cpp
ext_inst.opencl_test.cpp
fix_word_test.cpp
generator_magic_number_test.cpp

View File

@ -0,0 +1,90 @@
// 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.
// Assembler tests for non-semantic extended instructions
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"
using ::testing::Eq;
namespace spvtools {
namespace {
using NonSemanticRoundTripTest = RoundTripTest;
using NonSemanticTextToBinaryTest = spvtest::TextToBinaryTest;
TEST_F(NonSemanticRoundTripTest, NonSemanticInsts) {
std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
%1 = OpExtInstImport "NonSemantic.Testing.ExtInst"
%2 = OpTypeVoid
%3 = OpExtInst %2 %1 132384681 %2
%4 = OpTypeInt 32 0
%5 = OpConstant %4 123
%6 = OpString "Test string"
%7 = OpExtInst %4 %1 82198732 %5 %6
%8 = OpExtInstImport "NonSemantic.Testing.AnotherUnknownExtInstSet"
%9 = OpExtInst %4 %8 613874321 %7 %5 %6
)";
std::string disassembly = EncodeAndDecodeSuccessfully(
spirv, SPV_BINARY_TO_TEXT_OPTION_NONE, SPV_ENV_UNIVERSAL_1_0);
EXPECT_THAT(disassembly, Eq(spirv));
}
TEST_F(NonSemanticTextToBinaryTest, InvalidExtInstSetName) {
std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
%1 = OpExtInstImport "NonSemantic_Testing_ExtInst"
)";
EXPECT_THAT(
CompileFailure(spirv),
Eq("Invalid extended instruction import 'NonSemantic_Testing_ExtInst'"));
}
TEST_F(NonSemanticTextToBinaryTest, NonSemanticIntParameter) {
std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
%1 = OpExtInstImport "NonSemantic.Testing.ExtInst"
%2 = OpTypeVoid
%3 = OpExtInst %2 %1 1 99999
)";
EXPECT_THAT(CompileFailure(spirv), Eq("Expected id to start with %."));
}
TEST_F(NonSemanticTextToBinaryTest, NonSemanticFloatParameter) {
std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
%1 = OpExtInstImport "NonSemantic.Testing.ExtInst"
%2 = OpTypeVoid
%3 = OpExtInst %2 %1 1 3.141592
)";
EXPECT_THAT(CompileFailure(spirv), Eq("Expected id to start with %."));
}
TEST_F(NonSemanticTextToBinaryTest, NonSemanticStringParameter) {
std::string spirv = R"(OpExtension "SPV_KHR_non_semantic_info"
%1 = OpExtInstImport "NonSemantic.Testing.ExtInst"
%2 = OpTypeVoid
%3 = OpExtInst %2 %1 1 "foobar"
)";
EXPECT_THAT(CompileFailure(spirv), Eq("Expected id to start with %."));
}
} // namespace
} // namespace spvtools

View File

@ -152,6 +152,53 @@ TEST_F(StripDebugStringTest, OpNameRemoved) {
/* do_validation */ true);
}
TEST_F(StripDebugStringTest, OpStringRemovedWithNonSemantic) {
std::vector<const char*> input{
// clang-format off
"OpCapability Shader",
"OpExtension \"SPV_KHR_non_semantic_info\"",
"%1 = OpExtInstImport \"NonSemantic.Testing.Set\"",
"OpMemoryModel Logical GLSL450",
"OpEntryPoint Vertex %2 \"main\"",
// this string is not referenced, should be removed fully
"%3 = OpString \"minimal.vert\"",
"OpName %3 \"bob\"",
// this string is referenced and cannot be removed,
// but the name should be
"%4 = OpString \"secondary.inc\"",
"OpName %4 \"sue\"",
"%void = OpTypeVoid",
"%6 = OpTypeFunction %void",
"%2 = OpFunction %void None %6",
"%7 = OpLabel",
"%8 = OpExtInst %void %1 5 %4",
"OpReturn",
"OpFunctionEnd",
// clang-format on
};
std::vector<const char*> output{
// clang-format off
"OpCapability Shader",
"OpExtension \"SPV_KHR_non_semantic_info\"",
"%1 = OpExtInstImport \"NonSemantic.Testing.Set\"",
"OpMemoryModel Logical GLSL450",
"OpEntryPoint Vertex %2 \"main\"",
"%4 = OpString \"secondary.inc\"",
"%void = OpTypeVoid",
"%6 = OpTypeFunction %void",
"%2 = OpFunction %void None %6",
"%7 = OpLabel",
"%8 = OpExtInst %void %1 5 %4",
"OpReturn",
"OpFunctionEnd",
// clang-format on
};
SinglePassRunAndCheck<StripDebugInfoPass>(JoinAllInsts(input),
JoinAllInsts(output),
/* skip_nop = */ false,
/* do_validation */ true);
}
using StripDebugInfoTest = PassTest<::testing::TestWithParam<const char*>>;
TEST_P(StripDebugInfoTest, Kind) {

View File

@ -70,6 +70,7 @@ add_spvtools_unittest(TARGET val_fghijklmnop
val_memory_test.cpp
val_misc_test.cpp
val_modes_test.cpp
val_non_semantic_test.cpp
val_non_uniform_test.cpp
val_opencl_test.cpp
val_primitives_test.cpp

View File

@ -315,6 +315,243 @@ OpNop
"non-OpPhi instructions"));
}
TEST_F(ValidateAdjacency, NonSemanticBeforeOpPhiBad) {
const std::string body = R"(
OpSelectionMerge %end_label None
OpBranchConditional %true %true_label %false_label
%true_label = OpLabel
OpBranch %end_label
%false_label = OpLabel
OpBranch %end_label
%end_label = OpLabel
%dummy = OpExtInst %void %extinst 123 %int_1
%result = OpPhi %bool %true %true_label %false %false_label
)";
const std::string extra = R"(OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%extinst = OpExtInstImport "NonSemantic.Testing.Set"
)";
CompileSuccessfully(GenerateShaderCode(body, extra));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpPhi must appear within a non-entry block before all "
"non-OpPhi instructions"));
}
TEST_F(ValidateAdjacency, NonSemanticBetweenOpPhiBad) {
const std::string body = R"(
OpSelectionMerge %end_label None
OpBranchConditional %true %true_label %false_label
%true_label = OpLabel
OpBranch %end_label
%false_label = OpLabel
OpBranch %end_label
%end_label = OpLabel
%result1 = OpPhi %bool %true %true_label %false %false_label
%dummy = OpExtInst %void %extinst 123 %int_1
%result2 = OpPhi %bool %true %true_label %false %false_label
)";
const std::string extra = R"(OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%extinst = OpExtInstImport "NonSemantic.Testing.Set"
)";
CompileSuccessfully(GenerateShaderCode(body, extra));
EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpPhi must appear within a non-entry block before all "
"non-OpPhi instructions"));
}
TEST_F(ValidateAdjacency, NonSemanticAfterOpPhiGood) {
const std::string body = R"(
OpSelectionMerge %end_label None
OpBranchConditional %true %true_label %false_label
%true_label = OpLabel
OpBranch %end_label
%false_label = OpLabel
OpBranch %end_label
%end_label = OpLabel
OpLine %string 0 0
%result = OpPhi %bool %true %true_label %false %false_label
%dummy = OpExtInst %void %extinst 123 %int_1
)";
const std::string extra = R"(OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%extinst = OpExtInstImport "NonSemantic.Testing.Set"
)";
CompileSuccessfully(GenerateShaderCode(body, extra));
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateAdjacency, NonSemanticBeforeOpFunctionParameterBad) {
const std::string body = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%extinst = OpExtInstImport "NonSemantic.Testing.Set"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%string = OpString ""
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%zero = OpConstant %int 0
%int_1 = OpConstant %int 1
%func = OpTypeFunction %void
%func_int = OpTypePointer Function %int
%paramfunc_type = OpTypeFunction %void %int %int
%paramfunc = OpFunction %void None %paramfunc_type
%dummy = OpExtInst %void %extinst 123 %int_1
%a = OpFunctionParameter %int
%b = OpFunctionParameter %int
%paramfunc_entry = OpLabel
OpReturn
OpFunctionEnd
%main = OpFunction %void None %func
%main_entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body);
EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Non-semantic OpExtInst within function definition "
"must appear in a block"));
}
TEST_F(ValidateAdjacency, NonSemanticBetweenOpFunctionParameterBad) {
const std::string body = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%extinst = OpExtInstImport "NonSemantic.Testing.Set"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%string = OpString ""
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%zero = OpConstant %int 0
%int_1 = OpConstant %int 1
%func = OpTypeFunction %void
%func_int = OpTypePointer Function %int
%paramfunc_type = OpTypeFunction %void %int %int
%paramfunc = OpFunction %void None %paramfunc_type
%a = OpFunctionParameter %int
%dummy = OpExtInst %void %extinst 123 %int_1
%b = OpFunctionParameter %int
%paramfunc_entry = OpLabel
OpReturn
OpFunctionEnd
%main = OpFunction %void None %func
%main_entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body);
EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Non-semantic OpExtInst within function definition "
"must appear in a block"));
}
TEST_F(ValidateAdjacency, NonSemanticAfterOpFunctionParameterGood) {
const std::string body = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%extinst = OpExtInstImport "NonSemantic.Testing.Set"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%string = OpString ""
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%zero = OpConstant %int 0
%int_1 = OpConstant %int 1
%func = OpTypeFunction %void
%func_int = OpTypePointer Function %int
%paramfunc_type = OpTypeFunction %void %int %int
%paramfunc = OpFunction %void None %paramfunc_type
%a = OpFunctionParameter %int
%b = OpFunctionParameter %int
%paramfunc_entry = OpLabel
%dummy = OpExtInst %void %extinst 123 %int_1
OpReturn
OpFunctionEnd
%main = OpFunction %void None %func
%main_entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateAdjacency, NonSemanticBetweenFunctionsGood) {
const std::string body = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%extinst = OpExtInstImport "NonSemantic.Testing.Set"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%string = OpString ""
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%true = OpConstantTrue %bool
%false = OpConstantFalse %bool
%zero = OpConstant %int 0
%int_1 = OpConstant %int 1
%func = OpTypeFunction %void
%func_int = OpTypePointer Function %int
%paramfunc_type = OpTypeFunction %void %int %int
%paramfunc = OpFunction %void None %paramfunc_type
%a = OpFunctionParameter %int
%b = OpFunctionParameter %int
%paramfunc_entry = OpLabel
OpReturn
OpFunctionEnd
%dummy = OpExtInst %void %extinst 123 %int_1
%main = OpFunction %void None %func
%main_entry = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(body);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateAdjacency, OpVariableInFunctionGood) {
const std::string body = R"(
OpLine %string 1 1

View File

@ -0,0 +1,195 @@
// Copyright (c) 2019 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.
// Validation tests for non-semantic instructions
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "test/unit_spirv.h"
#include "test/val/val_code_generator.h"
#include "test/val/val_fixtures.h"
namespace spvtools {
namespace val {
namespace {
struct TestResult {
TestResult(spv_result_t in_validation_result = SPV_SUCCESS,
const char* in_error_str = nullptr,
const char* in_error_str2 = nullptr)
: validation_result(in_validation_result),
error_str(in_error_str),
error_str2(in_error_str2) {}
spv_result_t validation_result;
const char* error_str;
const char* error_str2;
};
using ::testing::Combine;
using ::testing::HasSubstr;
using ::testing::Values;
using ::testing::ValuesIn;
using ValidateNonSemanticGenerated = spvtest::ValidateBase<
std::tuple<bool, bool, const char*, const char*, TestResult>>;
using ValidateNonSemanticString = spvtest::ValidateBase<bool>;
CodeGenerator GetNonSemanticCodeGenerator(const bool declare_ext,
const bool declare_extinst,
const char* const global_extinsts,
const char* const function_extinsts) {
CodeGenerator generator = CodeGenerator::GetDefaultShaderCodeGenerator();
if (declare_ext) {
generator.extensions_ += "OpExtension \"SPV_KHR_non_semantic_info\"\n";
}
if (declare_extinst) {
generator.extensions_ +=
"%extinst = OpExtInstImport \"NonSemantic.Testing.Set\"\n";
}
generator.after_types_ = global_extinsts;
generator.before_types_ = "%decorate_group = OpDecorationGroup";
EntryPoint entry_point;
entry_point.name = "main";
entry_point.execution_model = "Vertex";
entry_point.body = R"(
)";
entry_point.body += function_extinsts;
generator.entry_points_.push_back(std::move(entry_point));
return generator;
}
TEST_P(ValidateNonSemanticGenerated, InTest) {
const bool declare_ext = std::get<0>(GetParam());
const bool declare_extinst = std::get<1>(GetParam());
const char* const global_extinsts = std::get<2>(GetParam());
const char* const function_extinsts = std::get<3>(GetParam());
const TestResult& test_result = std::get<4>(GetParam());
CodeGenerator generator = GetNonSemanticCodeGenerator(
declare_ext, declare_extinst, global_extinsts, function_extinsts);
CompileSuccessfully(generator.Build(), SPV_ENV_VULKAN_1_0);
ASSERT_EQ(test_result.validation_result,
ValidateInstructions(SPV_ENV_VULKAN_1_0));
if (test_result.error_str) {
EXPECT_THAT(getDiagnosticString(),
testing::ContainsRegex(test_result.error_str));
}
if (test_result.error_str2) {
EXPECT_THAT(getDiagnosticString(),
testing::ContainsRegex(test_result.error_str2));
}
}
INSTANTIATE_TEST_SUITE_P(OnlyOpExtension, ValidateNonSemanticGenerated,
Combine(Values(true), Values(false), Values(""),
Values(""), Values(TestResult())));
INSTANTIATE_TEST_SUITE_P(
MissingOpExtension, ValidateNonSemanticGenerated,
Combine(Values(false), Values(true), Values(""), Values(""),
Values(TestResult(
SPV_ERROR_INVALID_DATA,
"NonSemantic extended instruction sets cannot be declared "
"without SPV_KHR_non_semantic_info."))));
INSTANTIATE_TEST_SUITE_P(NoExtInst, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true), Values(""),
Values(""), Values(TestResult())));
INSTANTIATE_TEST_SUITE_P(
SimpleGlobalExtInst, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true),
Values("%result = OpExtInst %void %extinst 123 %i32"), Values(""),
Values(TestResult())));
INSTANTIATE_TEST_SUITE_P(
ComplexGlobalExtInst, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true),
Values("%result = OpExtInst %void %extinst 123 %i32 %u32_2 "
"%f32vec4_1234 %u32_0"),
Values(""), Values(TestResult())));
INSTANTIATE_TEST_SUITE_P(
SimpleFunctionLevelExtInst, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true), Values(""),
Values("%result = OpExtInst %void %extinst 123 %i32"),
Values(TestResult())));
INSTANTIATE_TEST_SUITE_P(
FunctionTypeReference, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true),
Values("%result = OpExtInst %void %extinst 123 %func"), Values(""),
Values(TestResult())));
INSTANTIATE_TEST_SUITE_P(
EntryPointReference, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true), Values(""),
Values("%result = OpExtInst %void %extinst 123 %main"),
Values(TestResult())));
INSTANTIATE_TEST_SUITE_P(
DecorationGroupReference, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true), Values(""),
Values("%result = OpExtInst %void %extinst 123 %decorate_group"),
Values(TestResult())));
INSTANTIATE_TEST_SUITE_P(
UnknownIDReference, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true),
Values("%result = OpExtInst %void %extinst 123 %undefined_id"),
Values(""),
Values(TestResult(SPV_ERROR_INVALID_ID,
"ID .* has not been defined"))));
INSTANTIATE_TEST_SUITE_P(
NonSemanticUseInSemantic, ValidateNonSemanticGenerated,
Combine(Values(true), Values(true),
Values("%result = OpExtInst %f32 %extinst 123 %i32\n"
"%invalid = OpConstantComposite %f32vec2 %f32_0 %result"),
Values(""),
Values(TestResult(SPV_ERROR_INVALID_ID,
"in semantic instruction cannot be a "
"non-semantic instruction"))));
TEST_F(ValidateNonSemanticString, InvalidSectionOpExtInst) {
const std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_non_semantic_info"
%extinst = OpExtInstImport "NonSemantic.Testing.Set"
%test = OpExtInst %void %extinst 4 %void
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main"
)";
CompileSuccessfully(spirv);
EXPECT_THAT(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
// there's no specific error for using an OpExtInst too early, it requires a
// type so by definition any use of a type in it will be an undefined ID
EXPECT_THAT(getDiagnosticString(),
HasSubstr("ID 2[%2] has not been defined"));
}
} // namespace
} // namespace val
} // namespace spvtools

View File

@ -467,7 +467,8 @@ Options (in lexicographical order):)",
printf(R"(
--strip-reflect
Remove all reflection information. For now, this covers
reflection information defined by SPV_GOOGLE_hlsl_functionality1.)");
reflection information defined by SPV_GOOGLE_hlsl_functionality1
and SPV_KHR_non_semantic_info)");
printf(R"(
--target-env=<env>
Set the target environment. Without this flag the target

View File

@ -30,6 +30,7 @@ SPV_AMD_gcn_shader
SPV_AMD_gpu_shader_half_float
SPV_AMD_gpu_shader_int16
SPV_AMD_shader_trinary_minmax
SPV_KHR_non_semantic_info
"""