SPIRV-Tools/source/validate.cpp

323 lines
12 KiB
C++
Raw Normal View History

// Copyright (c) 2015 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 <libspirv/libspirv.h>
#include "binary.h"
#include "diagnostic.h"
#include "instruction.h"
#include "opcode.h"
#include "operand.h"
#include "validate.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <vector>
#define spvCheckReturn(expression) \
if (spv_result_t error = (expression)) return error;
spv_result_t spvValidateOperandsString(const uint32_t *words,
const uint16_t wordCount,
spv_position position,
spv_diagnostic *pDiagnostic) {
const char *str = (const char *)words;
uint64_t strWordCount = strlen(str) / sizeof(uint32_t) + 1;
if (strWordCount < wordCount) {
DIAGNOSTIC << "Instruction word count is too short, string extends past "
"end of instruction.";
return SPV_WARNING;
}
return SPV_SUCCESS;
}
spv_result_t spvValidateOperandsLiteral(const uint32_t *words,
const uint32_t length,
const uint16_t maxLength,
spv_position position,
spv_diagnostic *pDiagnostic) {
// NOTE: A literal could either be a number consuming up to 2 words or a
// null terminated string.
(void)words;
(void)length;
(void)maxLength;
(void)position;
(void)pDiagnostic;
return SPV_UNSUPPORTED;
}
spv_result_t spvValidateOperandValue(const spv_operand_type_t type,
const uint32_t word,
const spv_operand_table operandTable,
spv_position position,
spv_diagnostic *pDiagnostic) {
switch (type) {
case SPV_OPERAND_TYPE_ID:
case SPV_OPERAND_TYPE_RESULT_ID: {
// NOTE: ID's are validated in SPV_VALIDATION_LEVEL_1, this is
// SPV_VALIDATION_LEVEL_0
} break;
case SPV_OPERAND_TYPE_LITERAL_NUMBER: {
// NOTE: Implicitly valid as they are encoded as 32 bit value
} break;
case SPV_OPERAND_TYPE_SOURCE_LANGUAGE:
case SPV_OPERAND_TYPE_EXECUTION_MODEL:
case SPV_OPERAND_TYPE_ADDRESSING_MODEL:
case SPV_OPERAND_TYPE_MEMORY_MODEL:
case SPV_OPERAND_TYPE_EXECUTION_MODE:
case SPV_OPERAND_TYPE_STORAGE_CLASS:
case SPV_OPERAND_TYPE_DIMENSIONALITY:
case SPV_OPERAND_TYPE_SAMPLER_ADDRESSING_MODE:
case SPV_OPERAND_TYPE_SAMPLER_FILTER_MODE:
case SPV_OPERAND_TYPE_FP_FAST_MATH_MODE:
case SPV_OPERAND_TYPE_FP_ROUNDING_MODE:
case SPV_OPERAND_TYPE_LINKAGE_TYPE:
case SPV_OPERAND_TYPE_ACCESS_QUALIFIER:
case SPV_OPERAND_TYPE_FUNCTION_PARAMETER_ATTRIBUTE:
case SPV_OPERAND_TYPE_DECORATION:
case SPV_OPERAND_TYPE_BUILT_IN:
case SPV_OPERAND_TYPE_SELECTION_CONTROL:
case SPV_OPERAND_TYPE_LOOP_CONTROL:
case SPV_OPERAND_TYPE_FUNCTION_CONTROL:
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS:
Use opcode operand definitions from SPIR-V specification generator. The assembler and disassembler now use a dynamically adjusted sequence of expected operand types. (Internally, it is a deque, for readability.) Both parsers repeatedly pull an expected operand type from the left of this pattern list, and try to match the next input token against it. The expected pattern is adjusted during the parse to accommodate: - an extended instruction's expected operands, depending on the extended instruction's index. - when an operand itself has operands - to handle sequences of zero or more operands, or pairs of operands. These are expanded lazily during the parse. Adds spv::OperandClass from the SPIR-V specification generator. Modifies spv_operand_desc_t: - adds hasResult, hasType, and operandClass array to the opcode description type. - "wordCount" is replaced with "numTypes", which counts the number of entries in operandTypes. And each of those describes a *logical* operand, including the type id for the instruction, and the result id for the instruction. A logical operand could be variable-width, such as a literal string. Adds opcode.inc, an automatically-generated table of operation descriptions, with one line to describe each core instruction. Externally, we have modified the SPIR-V spec doc generator to emit this file. (We have hacked this copy to use the old semantics for OpLine.) Inside the assembler, parsing an operand may fail with new error code SPV_FAIL_MATCH. For an optional operand, this is not fatal, but should trigger backtracking at a higher level. The spvTextIsStartOfNewInst checks the case of the third letter of what might be an opcode. So now, "OpenCL" does not look like an opcode name. In assembly, the EntryPoint name field is mandatory, but can be an empty string. Adjust tests for changes to: - OpSampedImage - OpTypeSampler
2015-08-27 17:03:52 +00:00
case SPV_OPERAND_TYPE_OPTIONAL_MEMORY_ACCESS:
case SPV_OPERAND_TYPE_EXECUTION_SCOPE:
case SPV_OPERAND_TYPE_GROUP_OPERATION:
case SPV_OPERAND_TYPE_KERNEL_ENQ_FLAGS:
case SPV_OPERAND_TYPE_KERNEL_PROFILING_INFO: {
spv_operand_desc operandEntry = nullptr;
spv_result_t error =
spvOperandTableValueLookup(operandTable, type, word, &operandEntry);
if (error) {
DIAGNOSTIC << "Invalid '" << spvOperandTypeStr(type) << "' operand '"
<< word << "'.";
return error;
}
} break;
default:
assert(0 && "Invalid operand types should already have been caught!");
}
return SPV_SUCCESS;
}
spv_result_t spvValidateBasic(const spv_instruction_t *pInsts,
const uint64_t instCount,
const spv_opcode_table opcodeTable,
const spv_operand_table operandTable,
spv_position position,
spv_diagnostic *pDiagnostic) {
for (uint64_t instIndex = 0; instIndex < instCount; ++instIndex) {
const uint32_t *words = pInsts[instIndex].words.data();
uint16_t wordCount;
Op opcode;
spvOpcodeSplit(words[0], &wordCount, &opcode);
spv_opcode_desc opcodeEntry = nullptr;
if (spvOpcodeTableValueLookup(opcodeTable, opcode, &opcodeEntry)) {
DIAGNOSTIC << "Invalid Opcode '" << opcode << "'.";
return SPV_ERROR_INVALID_BINARY;
}
position->index++;
if (opcodeEntry->numTypes > wordCount) {
DIAGNOSTIC << "Instruction word count '" << wordCount
<< "' is not small, expected at least '"
<< opcodeEntry->numTypes << "'.";
return SPV_ERROR_INVALID_BINARY;
}
spv_operand_desc operandEntry = nullptr;
for (uint16_t index = 1; index < pInsts[instIndex].words.size();
++index, position->index++) {
const uint32_t word = words[index];
Use opcode operand definitions from SPIR-V specification generator. The assembler and disassembler now use a dynamically adjusted sequence of expected operand types. (Internally, it is a deque, for readability.) Both parsers repeatedly pull an expected operand type from the left of this pattern list, and try to match the next input token against it. The expected pattern is adjusted during the parse to accommodate: - an extended instruction's expected operands, depending on the extended instruction's index. - when an operand itself has operands - to handle sequences of zero or more operands, or pairs of operands. These are expanded lazily during the parse. Adds spv::OperandClass from the SPIR-V specification generator. Modifies spv_operand_desc_t: - adds hasResult, hasType, and operandClass array to the opcode description type. - "wordCount" is replaced with "numTypes", which counts the number of entries in operandTypes. And each of those describes a *logical* operand, including the type id for the instruction, and the result id for the instruction. A logical operand could be variable-width, such as a literal string. Adds opcode.inc, an automatically-generated table of operation descriptions, with one line to describe each core instruction. Externally, we have modified the SPIR-V spec doc generator to emit this file. (We have hacked this copy to use the old semantics for OpLine.) Inside the assembler, parsing an operand may fail with new error code SPV_FAIL_MATCH. For an optional operand, this is not fatal, but should trigger backtracking at a higher level. The spvTextIsStartOfNewInst checks the case of the third letter of what might be an opcode. So now, "OpenCL" does not look like an opcode name. In assembly, the EntryPoint name field is mandatory, but can be an empty string. Adjust tests for changes to: - OpSampedImage - OpTypeSampler
2015-08-27 17:03:52 +00:00
// TODO(dneto): This strategy is inadequate for dealing with operations
// with varying kinds or numbers of logical operands. See the definition
// of spvBinaryOperandInfo for more.
// We should really parse the instruction and capture and use
// the elaborated list of logical operands generated as a side effect
// of the parse.
spv_operand_type_t type = spvBinaryOperandInfo(
word, index, opcodeEntry, operandTable, &operandEntry);
if (SPV_OPERAND_TYPE_LITERAL_STRING == type) {
spvCheckReturn(spvValidateOperandsString(
words + index, wordCount - index, position, pDiagnostic));
// NOTE: String literals are always at the end of Opcodes
break;
} else if (SPV_OPERAND_TYPE_LITERAL_NUMBER == type) {
spvCheckReturn(spvValidateOperandsLiteral(
words + index, wordCount - index, 2, position, pDiagnostic));
} else {
spvCheckReturn(spvValidateOperandValue(type, word, operandTable,
position, pDiagnostic));
}
}
}
return SPV_SUCCESS;
}
spv_result_t spvValidateIDs(const spv_instruction_t *pInsts,
const uint64_t count, const uint32_t bound,
const spv_opcode_table opcodeTable,
const spv_operand_table operandTable,
const spv_ext_inst_table extInstTable,
spv_position position,
spv_diagnostic *pDiagnostic) {
std::vector<spv_id_info_t> idUses;
std::vector<spv_id_info_t> idDefs;
for (uint64_t instIndex = 0; instIndex < count; ++instIndex) {
const uint32_t *words = pInsts[instIndex].words.data();
Op opcode;
spvOpcodeSplit(words[0], nullptr, &opcode);
spv_opcode_desc opcodeEntry = nullptr;
if (spvOpcodeTableValueLookup(opcodeTable, opcode, &opcodeEntry)) {
DIAGNOSTIC << "Invalid Opcode '" << opcode << "'.";
return SPV_ERROR_INVALID_BINARY;
}
spv_operand_desc operandEntry = nullptr;
position->index++; // NOTE: Account for Opcode word
for (uint16_t index = 1; index < pInsts[instIndex].words.size();
++index, position->index++) {
const uint32_t word = words[index];
spv_operand_type_t type = spvBinaryOperandInfo(
word, index, opcodeEntry, operandTable, &operandEntry);
if (SPV_OPERAND_TYPE_RESULT_ID == type || SPV_OPERAND_TYPE_ID == type) {
if (0 == word) {
DIAGNOSTIC << "Invalid ID of '0' is not allowed.";
return SPV_ERROR_INVALID_ID;
}
if (bound < word) {
DIAGNOSTIC << "Invalid ID '" << word << "' exceeds the bound '"
<< bound << "'.";
return SPV_ERROR_INVALID_ID;
}
}
if (SPV_OPERAND_TYPE_RESULT_ID == type) {
idDefs.push_back(
{word, opcodeEntry->opcode, &pInsts[instIndex], *position});
}
if (SPV_OPERAND_TYPE_ID == type) {
idUses.push_back({word, opcodeEntry->opcode, nullptr, *position});
}
}
}
// NOTE: Error on redefined ID
for (size_t outerIndex = 0; outerIndex < idDefs.size(); ++outerIndex) {
for (size_t innerIndex = 0; innerIndex < idDefs.size(); ++innerIndex) {
if (outerIndex == innerIndex) {
continue;
}
if (idDefs[outerIndex].id == idDefs[innerIndex].id) {
DIAGNOSTIC << "Multiply defined ID '" << idDefs[outerIndex].id << "'.";
return SPV_ERROR_INVALID_ID;
}
}
}
// NOTE: Validate ID usage, including use of undefined ID's
position->index = SPV_INDEX_INSTRUCTION;
if (spvValidateInstructionIDs(pInsts, count, idUses.data(), idUses.size(),
idDefs.data(), idDefs.size(), opcodeTable,
operandTable, extInstTable, position,
pDiagnostic))
return SPV_ERROR_INVALID_ID;
return SPV_SUCCESS;
}
spv_result_t spvValidate(const spv_binary binary,
const spv_opcode_table opcodeTable,
const spv_operand_table operandTable,
const spv_ext_inst_table extInstTable,
const uint32_t options, spv_diagnostic *pDiagnostic) {
if (!opcodeTable || !operandTable) return SPV_ERROR_INVALID_TABLE;
if (!pDiagnostic) return SPV_ERROR_INVALID_DIAGNOSTIC;
spv_endianness_t endian;
spv_position_t position = {};
if (spvBinaryEndianness(binary, &endian)) {
DIAGNOSTIC << "Invalid SPIR-V magic number.";
return SPV_ERROR_INVALID_BINARY;
}
spv_header_t header;
if (spvBinaryHeaderGet(binary, endian, &header)) {
DIAGNOSTIC << "Invalid SPIR-V header.";
return SPV_ERROR_INVALID_BINARY;
}
// NOTE: Copy each instruction for easier processing
std::vector<spv_instruction_t> instructions;
uint64_t index = SPV_INDEX_INSTRUCTION;
while (index < binary->wordCount) {
uint16_t wordCount;
Op opcode;
spvOpcodeSplit(spvFixWord(binary->code[index], endian), &wordCount,
&opcode);
spv_instruction_t inst;
spvInstructionCopy(&binary->code[index], opcode, wordCount, endian, &inst);
instructions.push_back(inst);
index += wordCount;
}
if (spvIsInBitfield(SPV_VALIDATE_BASIC_BIT, options)) {
position.index = SPV_INDEX_INSTRUCTION;
// TODO: Imcomplete implementation
spvCheckReturn(spvValidateBasic(instructions.data(), instructions.size(),
opcodeTable, operandTable, &position,
pDiagnostic));
}
if (spvIsInBitfield(SPV_VALIDATE_LAYOUT_BIT, options)) {
position.index = SPV_INDEX_INSTRUCTION;
// TODO: spvBinaryValidateLayout
}
if (spvIsInBitfield(SPV_VALIDATE_ID_BIT, options)) {
position.index = SPV_INDEX_INSTRUCTION;
spvCheckReturn(spvValidateIDs(instructions.data(), instructions.size(),
header.bound, opcodeTable, operandTable,
extInstTable, &position, pDiagnostic));
}
if (spvIsInBitfield(SPV_VALIDATE_RULES_BIT, options)) {
position.index = SPV_INDEX_INSTRUCTION;
// TODO: Specified validation rules...
}
return SPV_SUCCESS;
}