Validation refactor

* Organize passes into seperate files
* Remove module layout logic from Cfg pass
* Remove module layout logic from Functions class
* Refactor ModuleLayoutPass for readability
* Adapt consistent naming of layout sections (Stage/Section -> Section)
This commit is contained in:
Umar Arshad 2016-01-13 23:25:11 -05:00
parent 866b6ab9da
commit 1ddeb246eb
11 changed files with 587 additions and 332 deletions

View File

@ -135,8 +135,12 @@ set(SPIRV_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/source/text.cpp ${CMAKE_CURRENT_SOURCE_DIR}/source/text.cpp
${CMAKE_CURRENT_SOURCE_DIR}/source/text_handler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/source/text_handler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/source/validate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/source/validate.cpp
${CMAKE_CURRENT_SOURCE_DIR}/source/validate_types.cpp ${CMAKE_CURRENT_SOURCE_DIR}/source/validate_cfg.cpp
${CMAKE_CURRENT_SOURCE_DIR}/source/validate_id.cpp) ${CMAKE_CURRENT_SOURCE_DIR}/source/validate_id.cpp
${CMAKE_CURRENT_SOURCE_DIR}/source/validate_instruction.cpp
${CMAKE_CURRENT_SOURCE_DIR}/source/validate_layout.cpp
${CMAKE_CURRENT_SOURCE_DIR}/source/validate_ssa.cpp
${CMAKE_CURRENT_SOURCE_DIR}/source/validate_types.cpp)
add_library(${SPIRV_TOOLS} ${SPIRV_SOURCES}) add_library(${SPIRV_TOOLS} ${SPIRV_SOURCES})
default_compile_options(${SPIRV_TOOLS}) default_compile_options(${SPIRV_TOOLS})

View File

@ -260,9 +260,10 @@ typedef enum spv_validate_options_t {
SPV_VALIDATE_ID_BIT = SPV_BIT(2), SPV_VALIDATE_ID_BIT = SPV_BIT(2),
SPV_VALIDATE_RULES_BIT = SPV_BIT(3), SPV_VALIDATE_RULES_BIT = SPV_BIT(3),
SPV_VALIDATE_SSA_BIT = SPV_BIT(4), SPV_VALIDATE_SSA_BIT = SPV_BIT(4),
SPV_VALIDATE_INSTRUCTION_BIT = SPV_BIT(5),
SPV_VALIDATE_ALL = SPV_VALIDATE_BASIC_BIT | SPV_VALIDATE_LAYOUT_BIT | SPV_VALIDATE_ALL = SPV_VALIDATE_BASIC_BIT | SPV_VALIDATE_LAYOUT_BIT |
SPV_VALIDATE_ID_BIT | SPV_VALIDATE_RULES_BIT | SPV_VALIDATE_ID_BIT | SPV_VALIDATE_RULES_BIT |
SPV_VALIDATE_SSA_BIT, SPV_VALIDATE_SSA_BIT | SPV_VALIDATE_INSTRUCTION_BIT ,
SPV_FORCE_32_BIT_ENUM(spv_validation_options_t) SPV_FORCE_32_BIT_ENUM(spv_validation_options_t)
} spv_validate_options_t; } spv_validate_options_t;

View File

@ -26,6 +26,7 @@
#include "validate.h" #include "validate.h"
#include "validate_types.h" #include "validate_types.h"
#include "validate_passes.h"
#include "binary.h" #include "binary.h"
#include "diagnostic.h" #include "diagnostic.h"
@ -41,30 +42,23 @@
#include <cstdio> #include <cstdio>
#include <functional> #include <functional>
#include <iterator> #include <iterator>
#include <map>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <unordered_set>
#include <vector> #include <vector>
using std::function; using std::function;
using std::map;
using std::ostream_iterator; using std::ostream_iterator;
using std::placeholders::_1; using std::placeholders::_1;
using std::string; using std::string;
using std::stringstream; using std::stringstream;
using std::transform; using std::transform;
using std::unordered_set;
using std::vector; using std::vector;
using libspirv::CfgPass;
using libspirv::InstructionPass;
using libspirv::ModuleLayoutPass;
using libspirv::SsaPass;
using libspirv::ValidationState_t; using libspirv::ValidationState_t;
using libspirv::kLayoutFunctionDeclarations;
using libspirv::kLayoutFunctionDefinitions;
using libspirv::kLayoutMemoryModel;
using libspirv::FunctionDecl;
#define spvCheckReturn(expression) \
if (spv_result_t error = (expression)) return error;
#if 0 #if 0
spv_result_t spvValidateOperandsString(const uint32_t* words, spv_result_t spvValidateOperandsString(const uint32_t* words,
@ -309,108 +303,6 @@ spv_result_t setHeader(void* user_data, spv_endianness_t endian, uint32_t magic,
return SPV_SUCCESS; return SPV_SUCCESS;
} }
// Performs SSA validation on the IDs of an instruction. The
// can_have_forward_declared_ids functor should return true if the
// instruction operand's ID can be forward referenced.
//
// TODO(umar): Use dominators to correctly validate SSA. For example, the result
// id from a 'then' block cannot dominate its usage in the 'else' block. This
// is not yet performed by this funciton.
spv_result_t SsaPass(ValidationState_t& _,
function<bool(unsigned)> can_have_forward_declared_ids,
const spv_parsed_instruction_t* inst) {
if (_.is_enabled(SPV_VALIDATE_SSA_BIT)) {
for (unsigned i = 0; i < inst->num_operands; i++) {
const spv_parsed_operand_t& operand = inst->operands[i];
const spv_operand_type_t& type = operand.type;
const uint32_t* operand_ptr = inst->words + operand.offset;
auto ret = SPV_ERROR_INTERNAL;
switch (type) {
case SPV_OPERAND_TYPE_RESULT_ID:
_.removeIfForwardDeclared(*operand_ptr);
ret = _.defineId(*operand_ptr);
break;
case SPV_OPERAND_TYPE_ID:
case SPV_OPERAND_TYPE_TYPE_ID:
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
case SPV_OPERAND_TYPE_SCOPE_ID:
if (_.isDefinedId(*operand_ptr)) {
ret = SPV_SUCCESS;
} else if (can_have_forward_declared_ids(i)) {
ret = _.forwardDeclareId(*operand_ptr);
} else {
ret = _.diag(SPV_ERROR_INVALID_ID) << "ID "
<< _.getIdName(*operand_ptr)
<< " has not been defined";
}
break;
default:
ret = SPV_SUCCESS;
break;
}
if (SPV_SUCCESS != ret) {
return ret;
}
}
}
return SPV_SUCCESS;
}
// This funciton takes the opcode of an instruction and returns
// a function object that will return true if the index
// of the operand can be forwarad declared. This function will
// used in the SSA validation stage of the pipeline
function<bool(unsigned)> getCanBeForwardDeclaredFunction(SpvOp opcode) {
function<bool(unsigned index)> out;
switch (opcode) {
case SpvOpExecutionMode:
case SpvOpEntryPoint:
case SpvOpName:
case SpvOpMemberName:
case SpvOpSelectionMerge:
case SpvOpDecorate:
case SpvOpMemberDecorate:
case SpvOpBranch:
case SpvOpLoopMerge:
out = [](unsigned) { return true; };
break;
case SpvOpGroupDecorate:
case SpvOpGroupMemberDecorate:
case SpvOpBranchConditional:
case SpvOpSwitch:
out = [](unsigned index) { return index != 0; };
break;
case SpvOpFunctionCall:
out = [](unsigned index) { return index == 2; };
break;
case SpvOpPhi:
out = [](unsigned index) { return index > 1; };
break;
case SpvOpEnqueueKernel:
out = [](unsigned index) { return index == 8; };
break;
case SpvOpGetKernelNDrangeSubGroupCount:
case SpvOpGetKernelNDrangeMaxSubGroupSize:
out = [](unsigned index) { return index == 3; };
break;
case SpvOpGetKernelWorkGroupSize:
case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
out = [](unsigned index) { return index == 2; };
break;
default:
out = [](unsigned) { return false; };
break;
}
return out;
}
// Improves diagnostic messages by collecting names of IDs // Improves diagnostic messages by collecting names of IDs
// NOTE: This function returns void and is not involved in validation // NOTE: This function returns void and is not involved in validation
void DebugInstructionPass(ValidationState_t& _, void DebugInstructionPass(ValidationState_t& _,
@ -440,182 +332,18 @@ void DebugInstructionPass(ValidationState_t& _,
} }
} }
// TODO(umar): Check linkage capabilities for function declarations spv_result_t ProcessInstruction(void* user_data,
// TODO(umar): Better error messages
// NOTE: This function does not handle CFG related validation
// Performs logical layout validation. See Section 2.4
spv_result_t ModuleLayoutPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst) {
if (_.is_enabled(SPV_VALIDATE_LAYOUT_BIT)) {
SpvOp opcode = inst->opcode;
if (_.getLayoutStage() < kLayoutFunctionDeclarations) {
// Module scoped instructions are processed by determining if the opcode
// is part of the current stage. If it is not then the next stage is
// checked.
while (_.isOpcodeInCurrentLayoutStage(opcode) == false) {
_.progressToNextLayoutStageOrder();
if (_.getLayoutStage() == kLayoutMemoryModel &&
opcode != SpvOpMemoryModel) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< spvOpcodeString(opcode)
<< " cannot appear before the memory model instruction";
}
if (_.getLayoutStage() == kLayoutFunctionDeclarations) {
// All module stages have been processed. Recursivly call
// ModuleLayoutPass to process the next section of the module
return ModuleLayoutPass(_, inst);
}
}
if (opcode == SpvOpVariable) {
const uint32_t* storage_class = inst->words + inst->operands[2].offset;
if (*storage_class == SpvStorageClassFunction) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Variables cannot have a function[7] storage class "
"outside of a function";
}
}
} else if (_.getLayoutStage() == kLayoutFunctionDeclarations) {
if (_.isOpcodeInCurrentLayoutStage(opcode)) {
if (opcode == SpvOpVariable) {
const uint32_t* storage_class =
inst->words + inst->operands[2].offset;
if (*storage_class != SpvStorageClassFunction)
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "All Variable instructions in a function must have a "
"storage class of function[7]";
}
switch (opcode) {
case SpvOpFunction:
if (_.in_function_body()) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Cannot declare a function in a function body";
}
spvCheckReturn(_.get_functions().RegisterFunction(
inst->result_id, inst->type_id,
inst->words[inst->operands[2].offset],
inst->words[inst->operands[3].offset]));
break;
case SpvOpFunctionParameter:
if (_.in_function_body() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT) << "Function parameter "
"instructions must be "
"in a function body";
}
spvCheckReturn(_.get_functions().RegisterFunctionParameter(
inst->result_id, inst->type_id));
break;
case SpvOpLine: // ??
break;
case SpvOpLabel:
if (_.in_function_body() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Label instructions must be in a function body";
}
_.progressToNextLayoutStageOrder();
spvCheckReturn(_.get_functions().RegisterSetFunctionDeclType(
FunctionDecl::kFunctionDeclDefinition));
break;
case SpvOpFunctionEnd:
assert(_.get_functions().get_block_count() == 0 &&
"Function contains blocks in function declaration section");
if (_.in_function_body() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Function end instructions must be in a function body";
}
spvCheckReturn(_.get_functions().RegisterSetFunctionDeclType(
FunctionDecl::kFunctionDeclDeclaration));
spvCheckReturn(_.get_functions().RegisterFunctionEnd());
break;
default:
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "A function must begin with a label";
break;
}
} else {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< spvOpcodeString(opcode)
<< " cannot appear in a function declaration";
}
} else {
if (_.isOpcodeInCurrentLayoutStage(opcode) == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< " cannot appear in a funciton definition";
}
// NOTE: Additional checks will be performed in the CfgPass function
}
}
return SPV_SUCCESS;
}
// TODO(umar): Support for merge instructions
// TODO(umar): Structured control flow checks
spv_result_t CfgPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst) {
if (_.getLayoutStage() == kLayoutFunctionDefinitions) {
SpvOp opcode = inst->opcode;
switch (opcode) {
case SpvOpFunction:
spvCheckReturn(_.get_functions().RegisterFunction(
inst->result_id, inst->type_id,
inst->words[inst->operands[2].offset],
inst->words[inst->operands[3].offset]));
spvCheckReturn(_.get_functions().RegisterSetFunctionDeclType(
FunctionDecl::kFunctionDeclDefinition));
break;
case SpvOpFunctionParameter:
spvCheckReturn(_.get_functions().RegisterFunctionParameter(
inst->result_id, inst->type_id));
break;
case SpvOpFunctionEnd:
if (_.get_functions().get_block_count() == 0)
return _.diag(SPV_ERROR_INVALID_LAYOUT) << "Function declarations "
"must appear before "
"function definitions.";
spvCheckReturn(_.get_functions().RegisterFunctionEnd());
break;
case SpvOpLabel:
spvCheckReturn(_.get_functions().RegisterBlock(inst->result_id));
break;
case SpvOpBranch:
case SpvOpBranchConditional:
case SpvOpSwitch:
case SpvOpKill:
case SpvOpReturn:
case SpvOpReturnValue:
case SpvOpUnreachable:
spvCheckReturn(_.get_functions().RegisterBlockEnd());
break;
default:
if (_.in_block() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT) << spvOpcodeString(opcode)
<< " must appear in a block";
}
break;
}
}
return SPV_SUCCESS;
}
spv_result_t ProcessInstructions(void* user_data,
const spv_parsed_instruction_t* inst) { const spv_parsed_instruction_t* inst) {
ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data)); ValidationState_t& _ = *(reinterpret_cast<ValidationState_t*>(user_data));
_.incrementInstructionCount(); _.incrementInstructionCount();
auto can_have_forward_declared_ids =
getCanBeForwardDeclaredFunction(inst->opcode);
DebugInstructionPass(_, inst); DebugInstructionPass(_, inst);
// TODO(umar): Perform data rules pass // TODO(umar): Perform data rules pass
// TODO(umar): Perform instruction validation pass
spvCheckReturn(ModuleLayoutPass(_, inst)); spvCheckReturn(ModuleLayoutPass(_, inst));
spvCheckReturn(CfgPass(_, inst)); spvCheckReturn(CfgPass(_, inst));
spvCheckReturn(SsaPass(_, can_have_forward_declared_ids, inst)); spvCheckReturn(SsaPass(_, inst));
spvCheckReturn(InstructionPass(_, inst));
return SPV_SUCCESS; return SPV_SUCCESS;
} }
@ -645,7 +373,7 @@ spv_result_t spvValidate(const spv_const_context context,
ValidationState_t vstate(pDiagnostic, options); ValidationState_t vstate(pDiagnostic, options);
spvCheckReturn(spvBinaryParse(context, &vstate, binary->code, spvCheckReturn(spvBinaryParse(context, &vstate, binary->code,
binary->wordCount, setHeader, binary->wordCount, setHeader,
ProcessInstructions, pDiagnostic)); ProcessInstruction, pDiagnostic));
// TODO(umar): Add validation checks which require the parsing of the entire // TODO(umar): Add validation checks which require the parsing of the entire
// module. Use the information from the processInstructions pass to make // module. Use the information from the processInstructions pass to make

58
source/validate_cfg.cpp Normal file
View File

@ -0,0 +1,58 @@
// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
#include "validate_passes.h"
#include "validate_types.h"
namespace libspirv {
// TODO(umar): Support for merge instructions
// TODO(umar): Structured control flow checks
spv_result_t CfgPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst) {
if (_.getLayoutSection() == kLayoutFunctionDefinitions) {
SpvOp opcode = inst->opcode;
switch (opcode) {
case SpvOpLabel:
spvCheckReturn(_.get_functions().RegisterBlock(inst->result_id));
break;
case SpvOpBranch:
case SpvOpBranchConditional:
case SpvOpSwitch:
case SpvOpKill:
case SpvOpReturn:
case SpvOpReturnValue:
case SpvOpUnreachable:
spvCheckReturn(_.get_functions().RegisterBlockEnd());
break;
default:
break;
}
}
return SPV_SUCCESS;
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
// Performs validation on instructions that appear inside of a SPIR-V block.
#include "validate_passes.h"
#include "validate_types.h"
namespace libspirv {
spv_result_t InstructionPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst) {
if (_.is_enabled(SPV_VALIDATE_INSTRUCTION_BIT)) {
SpvOp opcode = inst->opcode;
switch (opcode) {
case SpvOpVariable: {
const uint32_t storage_class = inst->words[inst->operands[2].offset];
if (_.getLayoutSection() > kLayoutFunctionDeclarations) {
if (storage_class != SpvStorageClassFunction) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Variables must have a function[7] storage class inside"
" of a function";
}
} else {
if (storage_class == SpvStorageClassFunction) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Variables can not have a function[7] storage class "
"outside of a function";
}
}
} break;
default:
break;
}
}
return SPV_SUCCESS;
}
}

209
source/validate_layout.cpp Normal file
View File

@ -0,0 +1,209 @@
// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
// Source code for logical layout validation as described in section 2.4
#include "validate_types.h"
#include "validate_passes.h"
#include "libspirv/libspirv.h"
#include "diagnostic.h"
#include "opcode.h"
#include "operand.h"
#include <cassert>
using libspirv::ValidationState_t;
using libspirv::kLayoutMemoryModel;
using libspirv::kLayoutFunctionDeclarations;
using libspirv::kLayoutFunctionDefinitions;
using libspirv::FunctionDecl;
namespace {
// Module scoped instructions are processed by determining if the opcode
// is part of the current layout section. If it is not then the next sections is
// checked.
spv_result_t ModuleScopedInstructions(ValidationState_t& _,
const spv_parsed_instruction_t* inst,
SpvOp opcode) {
while (_.isOpcodeInCurrentLayoutSection(opcode) == false) {
_.progressToNextLayoutSectionOrder();
switch (_.getLayoutSection()) {
case kLayoutMemoryModel:
if (opcode != SpvOpMemoryModel) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< spvOpcodeString(opcode)
<< " cannot appear before the memory model instruction";
}
break;
case kLayoutFunctionDeclarations:
// All module sections have been processed. Recursivly call
// ModuleLayoutPass to process the next section of the module
return libspirv::ModuleLayoutPass(_, inst);
default:
break;
}
}
return SPV_SUCCESS;
}
// Function declaration validation is performed by making sure that the
// FunctionParameter and FunctionEnd instructions only appear inside of
// functions. It also ensures that the Function instruction does not appear
// inside of another function. This stage ends when the first label is
// encountered inside of a function.
spv_result_t FunctionScopedInstructions(ValidationState_t& _,
const spv_parsed_instruction_t* inst,
SpvOp opcode) {
if (_.isOpcodeInCurrentLayoutSection(opcode)) {
switch (opcode) {
case SpvOpFunction:
if (_.in_function_body()) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Cannot declare a function in a function body";
}
spvCheckReturn(_.get_functions().RegisterFunction(
inst->result_id, inst->type_id,
inst->words[inst->operands[2].offset],
inst->words[inst->operands[3].offset]));
if (_.getLayoutSection() == kLayoutFunctionDefinitions)
spvCheckReturn(_.get_functions().RegisterSetFunctionDeclType(
FunctionDecl::kFunctionDeclDefinition));
break;
case SpvOpFunctionParameter:
if (_.in_function_body() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT) << "Function parameter "
"instructions must be in "
"a function body";
}
if (_.get_functions().get_block_count() != 0) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Function parameters must only appear immediatly after the "
"function definition";
}
spvCheckReturn(_.get_functions().RegisterFunctionParameter(
inst->result_id, inst->type_id));
break;
case SpvOpFunctionEnd:
if (_.in_function_body() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Function end instructions must be in a function body";
}
if (_.in_block()) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Function end cannot be called in blocks";
}
if (_.get_functions().get_block_count() == 0 &&
_.getLayoutSection() == kLayoutFunctionDefinitions) {
return _.diag(SPV_ERROR_INVALID_LAYOUT) << "Function declarations "
"must appear before "
"function definitions.";
}
spvCheckReturn(_.get_functions().RegisterFunctionEnd());
if (_.getLayoutSection() == kLayoutFunctionDeclarations) {
spvCheckReturn(_.get_functions().RegisterSetFunctionDeclType(
FunctionDecl::kFunctionDeclDeclaration));
}
break;
case SpvOpLine: // ??
break;
case SpvOpLabel:
// If the label is encountered then the current function is a
// definition so set the function to a declaration and update the
// module section
if (_.in_function_body() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Label instructions must be in a function body";
}
if (_.in_block()) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "A block must end with a branch instruction.";
}
if (_.getLayoutSection() == kLayoutFunctionDeclarations) {
_.progressToNextLayoutSectionOrder();
spvCheckReturn(_.get_functions().RegisterSetFunctionDeclType(
FunctionDecl::kFunctionDeclDefinition));
}
break;
default:
if (_.getLayoutSection() == kLayoutFunctionDeclarations) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< "A function must begin with a label";
} else {
if (_.in_block() == false) {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< spvOpcodeString(opcode) << " must appear in a block";
}
}
break;
}
} else {
return _.diag(SPV_ERROR_INVALID_LAYOUT)
<< spvOpcodeString(opcode)
<< " cannot appear in a function declaration";
}
return SPV_SUCCESS;
}
}
namespace libspirv {
// TODO(umar): Check linkage capabilities for function declarations
// TODO(umar): Better error messages
// NOTE: This function does not handle CFG related validation
// Performs logical layout validation. See Section 2.4
spv_result_t ModuleLayoutPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst) {
if (_.is_enabled(SPV_VALIDATE_LAYOUT_BIT)) {
SpvOp opcode = inst->opcode;
switch (_.getLayoutSection()) {
case kLayoutCapabilities:
case kLayoutExtensions:
case kLayoutExtInstImport:
case kLayoutMemoryModel:
case kLayoutEntryPoint:
case kLayoutExecutionMode:
case kLayoutDebug1:
case kLayoutDebug2:
case kLayoutAnnotations:
case kLayoutTypes:
spvCheckReturn(ModuleScopedInstructions(_, inst, opcode));
break;
case kLayoutFunctionDeclarations:
case kLayoutFunctionDefinitions:
spvCheckReturn(FunctionScopedInstructions(_, inst, opcode));
break;
} // switch(getLayoutSection())
}
return SPV_SUCCESS;
}
}

55
source/validate_passes.h Normal file
View File

@ -0,0 +1,55 @@
// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
#ifndef LIBSPIRV_VALIDATE_PASSES_H_
#define LIBSPIRV_VALIDATE_PASSES_H_
#include "binary.h"
#include "validate_types.h"
namespace libspirv
{
// TODO(umar): Better docs
// Performs logical layout validation as described in section 2.4 of the SPIR-V spec
spv_result_t ModuleLayoutPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst);
// Performs Control Flow Graph validation of a module
spv_result_t CfgPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst);
// Performs SSA validation of a module
spv_result_t SsaPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst);
// Performs instruction validation.
spv_result_t InstructionPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst);
}
#endif

142
source/validate_ssa.cpp Normal file
View File

@ -0,0 +1,142 @@
// Copyright (c) 2015-2016 The Khronos Group Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and/or associated documentation files (the
// "Materials"), to deal in the Materials without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Materials, and to
// permit persons to whom the Materials are furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Materials.
//
// MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS
// KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS
// SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT
// https://www.khronos.org/registry/
//
// THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
// MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
#include "opcode.h"
#include "validate_passes.h"
#include <functional>
using std::function;
using libspirv::ValidationState_t;
namespace {
// This funciton takes the opcode of an instruction and returns
// a function object that will return true if the index
// of the operand can be forwarad declared. This function will
// used in the SSA validation stage of the pipeline
function<bool(unsigned)> getCanBeForwardDeclaredFunction(SpvOp opcode) {
function<bool(unsigned index)> out;
switch (opcode) {
case SpvOpExecutionMode:
case SpvOpEntryPoint:
case SpvOpName:
case SpvOpMemberName:
case SpvOpSelectionMerge:
case SpvOpDecorate:
case SpvOpMemberDecorate:
case SpvOpBranch:
case SpvOpLoopMerge:
out = [](unsigned) { return true; };
break;
case SpvOpGroupDecorate:
case SpvOpGroupMemberDecorate:
case SpvOpBranchConditional:
case SpvOpSwitch:
out = [](unsigned index) { return index != 0; };
break;
case SpvOpFunctionCall:
out = [](unsigned index) { return index == 2; };
break;
case SpvOpPhi:
out = [](unsigned index) { return index > 1; };
break;
case SpvOpEnqueueKernel:
out = [](unsigned index) { return index == 8; };
break;
case SpvOpGetKernelNDrangeSubGroupCount:
case SpvOpGetKernelNDrangeMaxSubGroupSize:
out = [](unsigned index) { return index == 3; };
break;
case SpvOpGetKernelWorkGroupSize:
case SpvOpGetKernelPreferredWorkGroupSizeMultiple:
out = [](unsigned index) { return index == 2; };
break;
default:
out = [](unsigned) { return false; };
break;
}
return out;
}
}
namespace libspirv {
// Performs SSA validation on the IDs of an instruction. The
// can_have_forward_declared_ids functor should return true if the
// instruction operand's ID can be forward referenced.
//
// TODO(umar): Use dominators to correctly validate SSA. For example, the result
// id from a 'then' block cannot dominate its usage in the 'else' block. This
// is not yet performed by this funciton.
spv_result_t SsaPass(ValidationState_t& _,
const spv_parsed_instruction_t* inst) {
auto can_have_forward_declared_ids =
getCanBeForwardDeclaredFunction(inst->opcode);
if (_.is_enabled(SPV_VALIDATE_SSA_BIT)) {
for (unsigned i = 0; i < inst->num_operands; i++) {
const spv_parsed_operand_t& operand = inst->operands[i];
const spv_operand_type_t& type = operand.type;
const uint32_t* operand_ptr = inst->words + operand.offset;
auto ret = SPV_ERROR_INTERNAL;
switch (type) {
case SPV_OPERAND_TYPE_RESULT_ID:
_.removeIfForwardDeclared(*operand_ptr);
ret = _.defineId(*operand_ptr);
break;
case SPV_OPERAND_TYPE_ID:
case SPV_OPERAND_TYPE_TYPE_ID:
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
case SPV_OPERAND_TYPE_SCOPE_ID:
if (_.isDefinedId(*operand_ptr)) {
ret = SPV_SUCCESS;
} else if (can_have_forward_declared_ids(i)) {
ret = _.forwardDeclareId(*operand_ptr);
} else {
ret = _.diag(SPV_ERROR_INVALID_ID) << "ID "
<< _.getIdName(*operand_ptr)
<< " has not been defined";
}
break;
default:
ret = SPV_SUCCESS;
break;
}
if (SPV_SUCCESS != ret) {
return ret;
}
}
}
return SPV_SUCCESS;
}
}

View File

@ -38,7 +38,20 @@ using std::find;
using std::string; using std::string;
using std::unordered_set; using std::unordered_set;
using std::vector; using std::vector;
using namespace libspirv;
using libspirv::kLayoutCapabilities;
using libspirv::kLayoutExtensions;
using libspirv::kLayoutExtInstImport;
using libspirv::kLayoutMemoryModel;
using libspirv::kLayoutEntryPoint;
using libspirv::kLayoutExecutionMode;
using libspirv::kLayoutDebug1;
using libspirv::kLayoutDebug2;
using libspirv::kLayoutAnnotations;
using libspirv::kLayoutTypes;
using libspirv::kLayoutFunctionDeclarations;
using libspirv::kLayoutFunctionDefinitions;
using libspirv::ModuleLayoutSection;
namespace { namespace {
bool IsInstructionInLayoutSection(ModuleLayoutSection layout, SpvOp op) { bool IsInstructionInLayoutSection(ModuleLayoutSection layout, SpvOp op) {
@ -258,11 +271,11 @@ int ValidationState_t::incrementInstructionCount() {
return instruction_counter_++; return instruction_counter_++;
} }
ModuleLayoutSection ValidationState_t::getLayoutStage() const { ModuleLayoutSection ValidationState_t::getLayoutSection() const {
return current_layout_stage_; return current_layout_stage_;
} }
void ValidationState_t::progressToNextLayoutStageOrder() { void ValidationState_t::progressToNextLayoutSectionOrder() {
// Guard against going past the last element(kLayoutFunctionDefinitions) // Guard against going past the last element(kLayoutFunctionDefinitions)
if (current_layout_stage_ <= kLayoutFunctionDefinitions) { if (current_layout_stage_ <= kLayoutFunctionDefinitions) {
current_layout_stage_ = current_layout_stage_ =
@ -270,7 +283,7 @@ void ValidationState_t::progressToNextLayoutStageOrder() {
} }
} }
bool ValidationState_t::isOpcodeInCurrentLayoutStage(SpvOp op) { bool ValidationState_t::isOpcodeInCurrentLayoutSection(SpvOp op) {
return IsInstructionInLayoutSection(current_layout_stage_, op); return IsInstructionInLayoutSection(current_layout_stage_, op);
} }
@ -322,15 +335,8 @@ spv_result_t Functions::RegisterFunctionParameter(uint32_t id,
assert(in_function_ == true && assert(in_function_ == true &&
"Function parameter instructions cannot be declared outside of a " "Function parameter instructions cannot be declared outside of a "
"function"); "function");
if (in_block()) { assert(in_block() == false &&
return module_.diag(SPV_ERROR_INVALID_LAYOUT) "Function parameters cannot be called in blocks");
<< "Function parameters cannot be called in blocks";
}
if (block_ids_.back().size() != 0) {
return module_.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Function parameters must only appear immediatly after the "
"function definition";
}
// TODO(umar): Validate function parameter type order and count // TODO(umar): Validate function parameter type order and count
// TODO(umar): Use these variables to validate parameter type // TODO(umar): Use these variables to validate parameter type
(void)id; (void)id;
@ -339,33 +345,19 @@ spv_result_t Functions::RegisterFunctionParameter(uint32_t id,
} }
spv_result_t Functions::RegisterSetFunctionDeclType(FunctionDecl type) { spv_result_t Functions::RegisterSetFunctionDeclType(FunctionDecl type) {
assert(in_function_ == true && assert(declaration_type_.back() == FunctionDecl::kFunctionDeclUnknown);
"Function can not be declared inside of another function"); declaration_type_.back() = type;
if (declaration_type_.size() <= 1 || type == *(end(declaration_type_) - 2) ||
type == FunctionDecl::kFunctionDeclDeclaration) {
declaration_type_.back() = type;
} else if (type == FunctionDecl::kFunctionDeclDeclaration) {
return module_.diag(SPV_ERROR_INVALID_LAYOUT)
<< "Function declartions must appear before function definitions";
} else {
declaration_type_.back() = type;
}
return SPV_SUCCESS; return SPV_SUCCESS;
} }
spv_result_t Functions::RegisterBlock(uint32_t id) { spv_result_t Functions::RegisterBlock(uint32_t id) {
assert(in_function_ == true && "Labels can only exsist in functions"); assert(in_function_ == true && "Blocks can only exsist in functions");
if (module_.getLayoutStage() == assert(in_block_ == false && "Blocks cannot be nested");
ModuleLayoutSection::kLayoutFunctionDeclarations) { assert(module_.getLayoutSection() !=
return module_.diag(SPV_ERROR_INVALID_LAYOUT) ModuleLayoutSection::kLayoutFunctionDeclarations &&
<< "Function declartions must appear before function definitions"; "Function declartions must appear before function definitions");
} assert(declaration_type_.back() == FunctionDecl::kFunctionDeclDefinition &&
if (declaration_type_.back() != FunctionDecl::kFunctionDeclDefinition) { "Function declaration type should have already been defined");
// NOTE: This should not happen. We should know that this function is a
// definition at this point.
return module_.diag(SPV_ERROR_INTERNAL)
<< "Function declaration type should have already been defined";
}
block_ids_.back().push_back(id); block_ids_.back().push_back(id);
in_block_ = true; in_block_ = true;
@ -375,24 +367,22 @@ spv_result_t Functions::RegisterBlock(uint32_t id) {
spv_result_t Functions::RegisterFunctionEnd() { spv_result_t Functions::RegisterFunctionEnd() {
assert(in_function_ == true && assert(in_function_ == true &&
"Function end can only be called in functions"); "Function end can only be called in functions");
if (in_block()) { assert(in_block_ == false &&
return module_.diag(SPV_ERROR_INVALID_LAYOUT) "Function end cannot be called inside a block");
<< "Function end cannot be called in blocks";
}
in_function_ = false; in_function_ = false;
return SPV_SUCCESS; return SPV_SUCCESS;
} }
spv_result_t Functions::RegisterBlockEnd() { spv_result_t Functions::RegisterBlockEnd() {
assert(in_function_ == true &&
"Branch instruction can only be called in a function");
assert(in_block_ == true && assert(in_block_ == true &&
"Branch instruction can only be called in a block"); "Branch instruction can only be called in a block");
in_block_ = false; in_block_ = false;
return SPV_SUCCESS; return SPV_SUCCESS;
} }
size_t Functions::get_block_count() { size_t Functions::get_block_count() const {
assert(in_function_ == true &&
"Branch instruction can only be called in a block");
return block_ids_.back().size(); return block_ids_.back().size();
} }
} }

View File

@ -110,7 +110,7 @@ class Functions {
spv_result_t RegisterBlockEnd(); spv_result_t RegisterBlockEnd();
// Returns the number of blocks in the current function being parsed // Returns the number of blocks in the current function being parsed
size_t get_block_count(); size_t get_block_count() const;
// Retuns true if called after a function instruction but before the // Retuns true if called after a function instruction but before the
// function end instruction // function end instruction
@ -188,13 +188,13 @@ class ValidationState_t {
int incrementInstructionCount(); int incrementInstructionCount();
// Returns the current layout section which is being processed // Returns the current layout section which is being processed
ModuleLayoutSection getLayoutStage() const; ModuleLayoutSection getLayoutSection() const;
// Increments the module_layout_order_stage_ // Increments the module_layout_order_stage_
void progressToNextLayoutStageOrder(); void progressToNextLayoutSectionOrder();
// Determines if the op instruction is part of the current stage // Determines if the op instruction is part of the current stage
bool isOpcodeInCurrentLayoutStage(SpvOp op); bool isOpcodeInCurrentLayoutSection(SpvOp op);
libspirv::DiagnosticStream diag(spv_result_t error_code) const; libspirv::DiagnosticStream diag(spv_result_t error_code) const;
@ -234,4 +234,8 @@ class ValidationState_t {
}; };
} }
#define spvCheckReturn(expression) \
if (spv_result_t error = (expression)) return error;
#endif #endif

View File

@ -252,7 +252,9 @@ TEST_F(ValidateLayout, VariableFunctionStorageGood) {
ASSERT_EQ(SPV_SUCCESS, ValidateInstructions()); ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
} }
TEST_F(ValidateLayout, VariableFunctionStorageBad) {
// TODO(umar): This function should be moved to another validation file
TEST_F(ValidateLayout, DISABLED_VariableFunctionStorageBad) {
char str[] = R"( char str[] = R"(
OpMemoryModel Logical GLSL450 OpMemoryModel Logical GLSL450
OpDecorate %var Restrict OpDecorate %var Restrict