Check BuiltIn Decoration rules.

When applied to a structure-type member, all members of that structure
type must also be decorated with BuiltIn. (No allowed mixing of built-in
variables and non-built-in variables within a single structure.)

When applied to a structure-type member, that structure type cannot be
contained as a member of another structure type.

There is at most one object per Storage Class that can contain a
structure type containing members decorated with BuiltIn, consumed per
entry-point.
This commit is contained in:
Ehsan Nasiri 2017-01-16 12:54:15 -05:00 committed by David Neto
parent 3a7df2cf1b
commit 9c71c572e5
7 changed files with 347 additions and 4 deletions

View File

@ -61,6 +61,7 @@ class Decoration {
void set_struct_member_index(uint32_t index) { struct_member_index_ = index; } void set_struct_member_index(uint32_t index) { struct_member_index_ = index; }
int struct_member_index() { return struct_member_index_; } int struct_member_index() { return struct_member_index_; }
int struct_member_index() const { return struct_member_index_; }
SpvDecoration dec_type() { return dec_type_; } SpvDecoration dec_type() { return dec_type_; }
SpvDecoration dec_type() const { return dec_type_; } SpvDecoration dec_type() const { return dec_type_; }
std::vector<uint32_t>& params() { return params_; } std::vector<uint32_t>& params() { return params_; }

View File

@ -126,10 +126,28 @@ class ValidationState_t {
/// instruction /// instruction
bool in_block() const; bool in_block() const;
/// Registers the given <id> as an Entry Point.
void RegisterEntryPointId(const uint32_t id) {
entry_points_.push_back(id);
entry_point_interfaces_.insert(std::make_pair(id, std::vector<uint32_t>()));
}
/// Returns a list of entry point function ids /// Returns a list of entry point function ids
std::vector<uint32_t>& entry_points() { return entry_points_; }
const std::vector<uint32_t>& entry_points() const { return entry_points_; } const std::vector<uint32_t>& entry_points() const { return entry_points_; }
/// Adds a new interface id to the interfaces of the given entry point.
void RegisterInterfaceForEntryPoint(uint32_t entry_point,
uint32_t interface) {
entry_point_interfaces_[entry_point].push_back(interface);
}
/// Returns the interfaces of a given entry point. If the given id is not a
/// valid Entry Point id, std::out_of_range exception is thrown.
const std::vector<uint32_t>& entry_point_interfaces(
uint32_t entry_point) const {
return entry_point_interfaces_.at(entry_point);
}
/// Inserts an <id> to the set of functions that are target of OpFunctionCall. /// Inserts an <id> to the set of functions that are target of OpFunctionCall.
void AddFunctionCallTarget(const uint32_t id) { void AddFunctionCallTarget(const uint32_t id) {
function_call_targets_.insert(id); function_call_targets_.insert(id);
@ -263,6 +281,15 @@ class ValidationState_t {
return struct_nesting_depth_[id]; return struct_nesting_depth_[id];
} }
/// Records that the structure type has a member decorated with a built-in.
void RegisterStructTypeWithBuiltInMember(uint32_t id) {
builtin_structs_.insert(id);
}
/// Returns true if the struct type with the given Id has a BuiltIn member.
bool IsStructTypeWithBuiltInMember(uint32_t id) const {
return (builtin_structs_.find(id) != builtin_structs_.end());
}
private: private:
ValidationState_t(const ValidationState_t&); ValidationState_t(const ValidationState_t&);
@ -303,6 +330,9 @@ class ValidationState_t {
/// IDs that are entry points, ie, arguments to OpEntryPoint. /// IDs that are entry points, ie, arguments to OpEntryPoint.
std::vector<uint32_t> entry_points_; std::vector<uint32_t> entry_points_;
/// Maps an entry point id to its interfaces.
std::unordered_map<uint32_t, std::vector<uint32_t>> entry_point_interfaces_;
/// Functions IDs that are target of OpFunctionCall. /// Functions IDs that are target of OpFunctionCall.
std::unordered_set<uint32_t> function_call_targets_; std::unordered_set<uint32_t> function_call_targets_;
@ -315,6 +345,9 @@ class ValidationState_t {
/// Set of Local Variable IDs ('Function' Storage Class) /// Set of Local Variable IDs ('Function' Storage Class)
std::unordered_set<uint32_t> local_vars_; std::unordered_set<uint32_t> local_vars_;
/// Set of struct types that have members with a BuiltIn decoration.
std::unordered_set<uint32_t> builtin_structs_;
/// Structure Nesting Depth /// Structure Nesting Depth
std::unordered_map<uint32_t, uint32_t> struct_nesting_depth_; std::unordered_map<uint32_t, uint32_t> struct_nesting_depth_;

View File

@ -121,7 +121,13 @@ spv_result_t ProcessInstruction(void* user_data,
ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data)); ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
_.increment_instruction_count(); _.increment_instruction_count();
if (static_cast<SpvOp>(inst->opcode) == SpvOpEntryPoint) { if (static_cast<SpvOp>(inst->opcode) == SpvOpEntryPoint) {
_.entry_points().push_back(inst->words[2]); const auto entry_point = inst->words[2];
_.RegisterEntryPointId(entry_point);
// Operand 3 and later are the <id> of interfaces for the entry point.
for (int i = 3; i < inst->num_operands; ++i) {
_.RegisterInterfaceForEntryPoint(entry_point,
inst->words[inst->operands[i].offset]);
}
} }
if (static_cast<SpvOp>(inst->opcode) == SpvOpFunctionCall) { if (static_cast<SpvOp>(inst->opcode) == SpvOpFunctionCall) {
_.AddFunctionCallTarget(inst->words[3]); _.AddFunctionCallTarget(inst->words[3]);

View File

@ -14,15 +14,32 @@
#include "validate.h" #include "validate.h"
#include <algorithm>
#include <string> #include <string>
#include "diagnostic.h" #include "diagnostic.h"
#include "opcode.h" #include "opcode.h"
#include "val/validation_state.h" #include "val/validation_state.h"
using libspirv::Decoration;
using libspirv::DiagnosticStream; using libspirv::DiagnosticStream;
using libspirv::ValidationState_t; using libspirv::ValidationState_t;
namespace {
// Returns whether the given structure type has any members with BuiltIn
// decoration.
bool isBuiltInStruct(uint32_t struct_id, ValidationState_t& vstate) {
const auto& decorations = vstate.id_decorations(struct_id);
return std::any_of(
decorations.begin(), decorations.end(), [](const Decoration& d) {
return SpvDecorationBuiltIn == d.dec_type() &&
Decoration::kInvalidMember != d.struct_member_index();
});
}
} // anonymous namespace
namespace libspirv { namespace libspirv {
// Validates that decorations have been applied properly. // Validates that decorations have been applied properly.
@ -48,7 +65,49 @@ spv_result_t ValidateDecorations(ValidationState_t& vstate) {
} }
} }
// TODO: Add more decoration validation code here. for (uint32_t entry_point : vstate.entry_points()) {
const auto& interfaces = vstate.entry_point_interfaces(entry_point);
int num_builtin_inputs = 0;
int num_builtin_outputs = 0;
for (auto interface : interfaces) {
Instruction* var_instr = vstate.FindDef(interface);
if (SpvOpVariable != var_instr->opcode()) {
return vstate.diag(SPV_ERROR_INVALID_ID)
<< "Interfaces passed to OpEntryPoint must be of type "
"OpTypeVariable. Found Op"
<< spvOpcodeString(static_cast<SpvOp>(var_instr->opcode()))
<< ".";
}
const uint32_t ptr_id = var_instr->word(1);
Instruction* ptr_instr = vstate.FindDef(ptr_id);
// It is guaranteed (by validator ID checks) that ptr_instr is
// OpTypePointer. Word 3 of this instruction is the type being pointed to.
const uint32_t type_id = ptr_instr->word(3);
Instruction* type_instr = vstate.FindDef(type_id);
const auto storage_class =
static_cast<SpvStorageClass>(var_instr->word(3));
if (storage_class != SpvStorageClassInput &&
storage_class != SpvStorageClassOutput) {
return vstate.diag(SPV_ERROR_INVALID_ID)
<< "OpEntryPoint interfaces must be OpVariables with "
"Storage Class of Input(1) or Output(3). Found Storage Class "
<< storage_class << " for Entry Point id " << entry_point << ".";
}
if (type_instr && SpvOpTypeStruct == type_instr->opcode() &&
isBuiltInStruct(type_id, vstate)) {
if (storage_class == SpvStorageClassInput) ++num_builtin_inputs;
if (storage_class == SpvStorageClassOutput) ++num_builtin_outputs;
if (num_builtin_inputs > 1 || num_builtin_outputs > 1) break;
}
}
if (num_builtin_inputs > 1 || num_builtin_outputs > 1) {
return vstate.diag(SPV_ERROR_INVALID_BINARY)
<< "There must be at most one object per Storage Class that can "
"contain a structure type containing members decorated with "
"BuiltIn, consumed per entry-point. Entry Point id "
<< entry_point << " does not meet this requirement.";
}
}
return SPV_SUCCESS; return SPV_SUCCESS;
} }

View File

@ -31,6 +31,7 @@
#include "val/validation_state.h" #include "val/validation_state.h"
using libspirv::ValidationState_t; using libspirv::ValidationState_t;
using libspirv::Decoration;
using std::function; using std::function;
using std::ignore; using std::ignore;
using std::make_pair; using std::make_pair;
@ -386,6 +387,8 @@ bool idUsage::isValid<SpvOpTypeRuntimeArray>(const spv_instruction_t* inst,
template <> template <>
bool idUsage::isValid<SpvOpTypeStruct>(const spv_instruction_t* inst, bool idUsage::isValid<SpvOpTypeStruct>(const spv_instruction_t* inst,
const spv_opcode_desc) { const spv_opcode_desc) {
ValidationState_t& vstate = const_cast<ValidationState_t&>(module_);
const uint32_t struct_id = inst->words[1];
for (size_t memberTypeIndex = 2; memberTypeIndex < inst->words.size(); for (size_t memberTypeIndex = 2; memberTypeIndex < inst->words.size();
++memberTypeIndex) { ++memberTypeIndex) {
auto memberTypeId = inst->words[memberTypeIndex]; auto memberTypeId = inst->words[memberTypeIndex];
@ -396,7 +399,17 @@ bool idUsage::isValid<SpvOpTypeStruct>(const spv_instruction_t* inst,
<< "' is not a type."; << "' is not a type.";
return false; return false;
} }
if (memberType && module_.IsForwardPointer(memberTypeId)) { if (SpvOpTypeStruct == memberType->opcode() &&
module_.IsStructTypeWithBuiltInMember(memberTypeId)) {
DIAG(memberTypeIndex)
<< "Structure <id> " << memberTypeId
<< " contains members with BuiltIn decoration. Therefore this "
"structure may not be contained as a member of another structure "
"type. Structure <id> "
<< struct_id << " contains structure <id> " << memberTypeId << ".";
return false;
}
if (module_.IsForwardPointer(memberTypeId)) {
if (memberType->opcode() != SpvOpTypePointer) { if (memberType->opcode() != SpvOpTypePointer) {
DIAG(memberTypeIndex) << "Found a forward reference to a non-pointer " DIAG(memberTypeIndex) << "Found a forward reference to a non-pointer "
"type in OpTypeStruct instruction."; "type in OpTypeStruct instruction.";
@ -418,6 +431,27 @@ bool idUsage::isValid<SpvOpTypeStruct>(const spv_instruction_t* inst,
} }
} }
} }
std::unordered_set<uint32_t> built_in_members;
for (auto decoration : vstate.id_decorations(struct_id)) {
if (decoration.dec_type() == SpvDecorationBuiltIn &&
decoration.struct_member_index() != Decoration::kInvalidMember) {
built_in_members.insert(decoration.struct_member_index());
}
}
int num_struct_members = static_cast<int>(inst->words.size() - 2);
int num_builtin_members = static_cast<int>(built_in_members.size());
if (num_builtin_members > 0 && num_builtin_members != num_struct_members) {
DIAG(0)
<< "When BuiltIn decoration is applied to a structure-type member, "
"all members of that structure type must also be decorated with "
"BuiltIn (No allowed mixing of built-in variables and "
"non-built-in variables within a single structure). Structure id "
<< struct_id << " does not meet this requirement.";
return false;
}
if (num_builtin_members > 0) {
vstate.RegisterStructTypeWithBuiltInMember(struct_id);
}
return true; return true;
} }

View File

@ -178,5 +178,164 @@ TEST_F(ValidateDecorations, LinkageExportUsedForInitializedVariableGood) {
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState()); EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
} }
TEST_F(ValidateDecorations, StructAllMembersHaveBuiltInDecorationsGood) {
string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpMemberDecorate %_struct_1 0 BuiltIn Position
OpMemberDecorate %_struct_1 1 BuiltIn Position
OpMemberDecorate %_struct_1 2 BuiltIn Position
OpMemberDecorate %_struct_1 3 BuiltIn Position
%float = OpTypeFloat 32
%_runtimearr = OpTypeRuntimeArray %float
%_struct_1 = OpTypeStruct %float %float %float %_runtimearr
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
TEST_F(ValidateDecorations, MixedBuiltInDecorationsBad) {
string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpMemberDecorate %_struct_1 0 BuiltIn Position
OpMemberDecorate %_struct_1 1 BuiltIn Position
%float = OpTypeFloat 32
%_runtimearr = OpTypeRuntimeArray %float
%_struct_1 = OpTypeStruct %float %float %float %_runtimearr
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("When BuiltIn decoration is applied to a structure-type "
"member, all members of that structure type must also be "
"decorated with BuiltIn (No allowed mixing of built-in "
"variables and non-built-in variables within a single "
"structure). Structure id 1 does not meet this requirement."));
}
TEST_F(ValidateDecorations, StructContainsBuiltInStructBad) {
string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpMemberDecorate %_struct_1 0 BuiltIn Position
OpMemberDecorate %_struct_1 1 BuiltIn Position
OpMemberDecorate %_struct_1 2 BuiltIn Position
OpMemberDecorate %_struct_1 3 BuiltIn Position
%float = OpTypeFloat 32
%_runtimearr = OpTypeRuntimeArray %float
%_struct_1 = OpTypeStruct %float %float %float %_runtimearr
%_struct_2 = OpTypeStruct %_struct_1
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateAndRetrieveValidationState());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Structure <id> 1 contains members with BuiltIn "
"decoration. Therefore this structure may not be "
"contained as a member of another structure type. "
"Structure <id> 4 contains structure <id> 1."));
}
TEST_F(ValidateDecorations, StructContainsNonBuiltInStructGood) {
string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%float = OpTypeFloat 32
%_struct_1 = OpTypeStruct %float
%_struct_2 = OpTypeStruct %_struct_1
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
TEST_F(ValidateDecorations, MultipleBuiltInObjectsConsumedByOpEntryPointBad) {
string spirv = R"(
OpCapability Shader
OpCapability Geometry
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %main "main" %in_1 %in_2
OpMemberDecorate %struct_1 0 BuiltIn InvocationId
OpMemberDecorate %struct_2 0 BuiltIn Position
%int = OpTypeInt 32 1
%void = OpTypeVoid
%func = OpTypeFunction %void
%float = OpTypeFloat 32
%struct_1 = OpTypeStruct %int
%struct_2 = OpTypeStruct %float
%ptr_builtin_1 = OpTypePointer Input %struct_1
%ptr_builtin_2 = OpTypePointer Input %struct_2
%in_1 = OpVariable %ptr_builtin_1 Input
%in_2 = OpVariable %ptr_builtin_2 Input
%main = OpFunction %void None %func
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_BINARY, ValidateAndRetrieveValidationState());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("There must be at most one object per Storage Class "
"that can contain a structure type containing members "
"decorated with BuiltIn, consumed per entry-point."));
}
TEST_F(ValidateDecorations,
OneBuiltInObjectPerStorageClassConsumedByOpEntryPointGood) {
string spirv = R"(
OpCapability Shader
OpCapability Geometry
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %main "main" %in_1 %out_1
OpMemberDecorate %struct_1 0 BuiltIn InvocationId
OpMemberDecorate %struct_2 0 BuiltIn Position
%int = OpTypeInt 32 1
%void = OpTypeVoid
%func = OpTypeFunction %void
%float = OpTypeFloat 32
%struct_1 = OpTypeStruct %int
%struct_2 = OpTypeStruct %float
%ptr_builtin_1 = OpTypePointer Input %struct_1
%ptr_builtin_2 = OpTypePointer Output %struct_2
%in_1 = OpVariable %ptr_builtin_1 Input
%out_1 = OpVariable %ptr_builtin_2 Output
%main = OpFunction %void None %func
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
TEST_F(ValidateDecorations, NoBuiltInObjectsConsumedByOpEntryPointGood) {
string spirv = R"(
OpCapability Shader
OpCapability Geometry
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %main "main" %in_1 %out_1
%int = OpTypeInt 32 1
%void = OpTypeVoid
%func = OpTypeFunction %void
%float = OpTypeFloat 32
%struct_1 = OpTypeStruct %int
%struct_2 = OpTypeStruct %float
%ptr_builtin_1 = OpTypePointer Input %struct_1
%ptr_builtin_2 = OpTypePointer Output %struct_2
%in_1 = OpVariable %ptr_builtin_1 Input
%out_1 = OpVariable %ptr_builtin_2 Output
%main = OpFunction %void None %func
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateAndRetrieveValidationState());
}
} // anonymous namespace } // anonymous namespace

View File

@ -326,6 +326,57 @@ TEST_F(ValidateIdWithMessage, OpEntryPointReturnTypeBad) {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions()); EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
} }
TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceIsNotVariableTypeBad) {
string spirv = R"(
OpCapability Shader
OpCapability Geometry
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %main "main" %ptr_builtin_1
OpMemberDecorate %struct_1 0 BuiltIn InvocationId
%int = OpTypeInt 32 1
%void = OpTypeVoid
%func = OpTypeFunction %void
%struct_1 = OpTypeStruct %int
%ptr_builtin_1 = OpTypePointer Input %struct_1
%main = OpFunction %void None %func
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Interfaces passed to OpEntryPoint must be of type "
"OpTypeVariable. Found OpTypePointer."));
}
TEST_F(ValidateIdWithMessage, OpEntryPointInterfaceStorageClassBad) {
string spirv = R"(
OpCapability Shader
OpCapability Geometry
OpMemoryModel Logical GLSL450
OpEntryPoint Geometry %main "main" %in_1
OpMemberDecorate %struct_1 0 BuiltIn InvocationId
%int = OpTypeInt 32 1
%void = OpTypeVoid
%func = OpTypeFunction %void
%struct_1 = OpTypeStruct %int
%ptr_builtin_1 = OpTypePointer Uniform %struct_1
%in_1 = OpVariable %ptr_builtin_1 Uniform
%main = OpFunction %void None %func
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("OpEntryPoint interfaces must be OpVariables with "
"Storage Class of Input(1) or Output(3). Found Storage "
"Class 2 for Entry Point id 1."));
}
TEST_F(ValidateIdWithMessage, OpExecutionModeGood) { TEST_F(ValidateIdWithMessage, OpExecutionModeGood) {
string spirv = kGLSL450MemoryModel + R"( string spirv = kGLSL450MemoryModel + R"(
OpEntryPoint GLCompute %3 "" OpEntryPoint GLCompute %3 ""