mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-29 22:41:03 +00:00
11d5924227
Currently the validator, when checking an instruction is in the correct section, always advances the current section. This means if we have an instruction from a previous section we'll end up reporting it as invalid in a function definition. This error is confusing. This CL updates the validator to check if the given opcode is from a previous layout section before advancing the current section. If it is from a previous layout section an error is emitted.
358 lines
14 KiB
C++
358 lines
14 KiB
C++
// Copyright (c) 2015-2016 The Khronos Group Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Source code for logical layout validation as described in section 2.4
|
|
|
|
#include <cassert>
|
|
|
|
#include "DebugInfo.h"
|
|
#include "OpenCLDebugInfo100.h"
|
|
#include "source/diagnostic.h"
|
|
#include "source/opcode.h"
|
|
#include "source/operand.h"
|
|
#include "source/val/function.h"
|
|
#include "source/val/instruction.h"
|
|
#include "source/val/validate.h"
|
|
#include "source/val/validation_state.h"
|
|
|
|
namespace spvtools {
|
|
namespace val {
|
|
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 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 if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
|
|
const uint32_t ext_inst_index = inst->word(4);
|
|
bool local_debug_info = false;
|
|
if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
|
|
const OpenCLDebugInfo100Instructions ext_inst_key =
|
|
OpenCLDebugInfo100Instructions(ext_inst_index);
|
|
if (ext_inst_key == OpenCLDebugInfo100DebugScope ||
|
|
ext_inst_key == OpenCLDebugInfo100DebugNoScope ||
|
|
ext_inst_key == OpenCLDebugInfo100DebugDeclare ||
|
|
ext_inst_key == OpenCLDebugInfo100DebugValue) {
|
|
local_debug_info = true;
|
|
}
|
|
} else {
|
|
const DebugInfoInstructions ext_inst_key =
|
|
DebugInfoInstructions(ext_inst_index);
|
|
if (ext_inst_key == DebugInfoDebugScope ||
|
|
ext_inst_key == DebugInfoDebugNoScope ||
|
|
ext_inst_key == DebugInfoDebugDeclare ||
|
|
ext_inst_key == DebugInfoDebugValue) {
|
|
local_debug_info = true;
|
|
}
|
|
}
|
|
|
|
if (local_debug_info) {
|
|
if (_.in_function_body() == false) {
|
|
// DebugScope, DebugNoScope, DebugDeclare, DebugValue must
|
|
// appear in a function body.
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
|
|
<< "of debug info extension must appear in a function "
|
|
<< "body";
|
|
}
|
|
} else {
|
|
// Debug info extinst opcodes other than DebugScope, DebugNoScope,
|
|
// DebugDeclare, DebugValue must be placed between section 9 (types,
|
|
// constants, global variables) and section 10 (function
|
|
// declarations).
|
|
if (_.current_layout_section() < kLayoutTypes ||
|
|
_.current_layout_section() >= kLayoutFunctionDeclarations) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "Debug info extension instructions other than "
|
|
<< "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
|
|
<< "must appear between section 9 (types, constants, "
|
|
<< "global variables) and section 10 (function "
|
|
<< "declarations)";
|
|
}
|
|
}
|
|
} 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) {
|
|
if (_.IsOpcodeInPreviousLayoutSection(opcode)) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< spvOpcodeString(opcode) << " is in an invalid layout section";
|
|
}
|
|
|
|
_.ProgressToNextLayoutSectionOrder();
|
|
|
|
switch (_.current_layout_section()) {
|
|
case kLayoutMemoryModel:
|
|
if (opcode != SpvOpMemoryModel) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< spvOpcodeString(opcode)
|
|
<< " cannot appear before the memory model instruction";
|
|
}
|
|
break;
|
|
case kLayoutFunctionDeclarations:
|
|
// All module sections have been processed. Recursively call
|
|
// ModuleLayoutPass to process the next section of the module
|
|
return 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 Instruction* inst, SpvOp opcode) {
|
|
// Make sure we advance into the function definitions when we hit
|
|
// non-function declaration instructions.
|
|
if (_.current_layout_section() == kLayoutFunctionDeclarations &&
|
|
!_.IsOpcodeInCurrentLayoutSection(opcode)) {
|
|
_.ProgressToNextLayoutSectionOrder();
|
|
|
|
if (_.in_function_body()) {
|
|
if (auto error = _.current_function().RegisterSetFunctionDeclType(
|
|
FunctionDecl::kFunctionDeclDefinition)) {
|
|
return error;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (_.IsOpcodeInCurrentLayoutSection(opcode)) {
|
|
switch (opcode) {
|
|
case SpvOpFunction: {
|
|
if (_.in_function_body()) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "Cannot declare a function in a function body";
|
|
}
|
|
auto control_mask = inst->GetOperandAs<SpvFunctionControlMask>(2);
|
|
if (auto error =
|
|
_.RegisterFunction(inst->id(), inst->type_id(), control_mask,
|
|
inst->GetOperandAs<uint32_t>(3)))
|
|
return error;
|
|
if (_.current_layout_section() == kLayoutFunctionDefinitions) {
|
|
if (auto error = _.current_function().RegisterSetFunctionDeclType(
|
|
FunctionDecl::kFunctionDeclDefinition))
|
|
return error;
|
|
}
|
|
} break;
|
|
|
|
case SpvOpFunctionParameter:
|
|
if (_.in_function_body() == false) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "Function parameter instructions must be in a "
|
|
"function body";
|
|
}
|
|
if (_.current_function().block_count() != 0) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "Function parameters must only appear immediately after "
|
|
"the function definition";
|
|
}
|
|
if (auto error = _.current_function().RegisterFunctionParameter(
|
|
inst->id(), inst->type_id()))
|
|
return error;
|
|
break;
|
|
|
|
case SpvOpFunctionEnd:
|
|
if (_.in_function_body() == false) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "Function end instructions must be in a function body";
|
|
}
|
|
if (_.in_block()) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "Function end cannot be called in blocks";
|
|
}
|
|
if (_.current_function().block_count() == 0 &&
|
|
_.current_layout_section() == kLayoutFunctionDefinitions) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "Function declarations must appear before "
|
|
"function definitions.";
|
|
}
|
|
if (_.current_layout_section() == kLayoutFunctionDeclarations) {
|
|
if (auto error = _.current_function().RegisterSetFunctionDeclType(
|
|
FunctionDecl::kFunctionDeclDeclaration))
|
|
return error;
|
|
}
|
|
if (auto error = _.RegisterFunctionEnd()) return error;
|
|
break;
|
|
|
|
case SpvOpLine:
|
|
case SpvOpNoLine:
|
|
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, inst)
|
|
<< "Label instructions must be in a function body";
|
|
}
|
|
if (_.in_block()) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "A block must end with a branch instruction.";
|
|
}
|
|
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 if (spvExtInstIsDebugInfo(inst->ext_inst_type())) {
|
|
const uint32_t ext_inst_index = inst->word(4);
|
|
bool local_debug_info = false;
|
|
if (inst->ext_inst_type() == SPV_EXT_INST_TYPE_OPENCL_DEBUGINFO_100) {
|
|
const OpenCLDebugInfo100Instructions ext_inst_key =
|
|
OpenCLDebugInfo100Instructions(ext_inst_index);
|
|
if (ext_inst_key == OpenCLDebugInfo100DebugScope ||
|
|
ext_inst_key == OpenCLDebugInfo100DebugNoScope ||
|
|
ext_inst_key == OpenCLDebugInfo100DebugDeclare ||
|
|
ext_inst_key == OpenCLDebugInfo100DebugValue) {
|
|
local_debug_info = true;
|
|
}
|
|
} else {
|
|
const DebugInfoInstructions ext_inst_key =
|
|
DebugInfoInstructions(ext_inst_index);
|
|
if (ext_inst_key == DebugInfoDebugScope ||
|
|
ext_inst_key == DebugInfoDebugNoScope ||
|
|
ext_inst_key == DebugInfoDebugDeclare ||
|
|
ext_inst_key == DebugInfoDebugValue) {
|
|
local_debug_info = true;
|
|
}
|
|
}
|
|
|
|
if (local_debug_info) {
|
|
if (_.in_function_body() == false) {
|
|
// DebugScope, DebugNoScope, DebugDeclare, DebugValue must
|
|
// appear in a function body.
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
|
|
<< "of debug info extension must appear in a function "
|
|
<< "body";
|
|
}
|
|
} else {
|
|
// Debug info extinst opcodes other than DebugScope, DebugNoScope,
|
|
// DebugDeclare, DebugValue must be placed between section 9 (types,
|
|
// constants, global variables) and section 10 (function
|
|
// declarations).
|
|
if (_.current_layout_section() < kLayoutTypes ||
|
|
_.current_layout_section() >= kLayoutFunctionDeclarations) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "Debug info extension instructions other than "
|
|
<< "DebugScope, DebugNoScope, DebugDeclare, DebugValue "
|
|
<< "must appear between section 9 (types, constants, "
|
|
<< "global variables) and section 10 (function "
|
|
<< "declarations)";
|
|
}
|
|
}
|
|
} 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()) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< "A function must begin with a label";
|
|
} else {
|
|
if (_.in_block() == false) {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< spvOpcodeString(opcode) << " must appear in a block";
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
return _.diag(SPV_ERROR_INVALID_LAYOUT, inst)
|
|
<< spvOpcodeString(opcode)
|
|
<< " cannot appear in a function declaration";
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// 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 Instruction* inst) {
|
|
const SpvOp opcode = inst->opcode();
|
|
|
|
switch (_.current_layout_section()) {
|
|
case kLayoutCapabilities:
|
|
case kLayoutExtensions:
|
|
case kLayoutExtInstImport:
|
|
case kLayoutMemoryModel:
|
|
case kLayoutEntryPoint:
|
|
case kLayoutExecutionMode:
|
|
case kLayoutDebug1:
|
|
case kLayoutDebug2:
|
|
case kLayoutDebug3:
|
|
case kLayoutAnnotations:
|
|
case kLayoutTypes:
|
|
if (auto error = ModuleScopedInstructions(_, inst, opcode)) return error;
|
|
break;
|
|
case kLayoutFunctionDeclarations:
|
|
case kLayoutFunctionDefinitions:
|
|
if (auto error = FunctionScopedInstructions(_, inst, opcode)) {
|
|
return error;
|
|
}
|
|
break;
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
} // namespace val
|
|
} // namespace spvtools
|