SPIRV-Tools/source/val/validate_id.cpp
alan-baker 3ab0d22608
Support SPV_KHR_untyped_pointers (#5736)
* Support SPV_KHR_untyped_pointers

Covers:
- assembler
- disassembler
- validator

fix copyright

Validate OpTypeUntypedPointerKHR

* Disallow an untyped pointer in a typed pointer
* Validate capability requirements for untyped pointer
* Allow duplicate untyped pointer declarations

Add round trip tests

Validate OpUntypedVariableKHR

Validate untyped access chains

* Add a test for opcodes that generate untyped pointers
* simplify some checks for operands needing types
* validate OpUnypedAccessChainKHR, OpUntypedInBoundsAccessChainKHR,
  OpUntypedPtrAccessChainKHR, OpUntypedInBoundsPtrAccessChainKHR

Unify variable validation

Validate OpCopyMemorySized

* Fix some opcode tests to accound for untyped pointers
* Add validation for OpCopyMemorySized for shaders and untyped pointers
* fix up tests

Validate pointer comparisons and bitcast

* Update more helpers
* Fix entry validation to allow OpUntypedVariableKHR
* Validate OpPtrEqual, OpPtrNotEqual and OpPtrDiff
* Validate OpBitcast

Validate atomics and untyped pointers

Make interface variable validation aware of untyped pointers

* Check OpUntypedVariableKHR in interface validation

More untyped pointer validation

* Validate interfaces more thoroughly
* Validate layouts for untyped pointer uses
* Improve capability checks for vulkan with OpTypeUntypedPointerKHR
* workgroup member explicit layout validation updates

More validation

* validate function arguments and parameters
* handle untyped pointer and variable in more places

Add a friendly assembly name for untyped pointers

Update OpCopyMemory validation and tests

Fix test for token update

Fixes for validation

* Allow typed pointers to contain untyped pointers
* Fix decoration validation
* add untyped pointer as a case for size and alignments

Fix interface validation

* Grabbed the wrong storage class operand for untyped variables
* Add ability to specify assembler options in validation tests

Add passthrough validation for OpUntypedArrayLengthKHR

More validation of untyped pointers

* Validate OpUntypedArrayLengthKHR
* Validate layout for OpLoad, OpStore, and OpUntypedArrayLengthKHR

Validation support for cooperative matrix and untyped pointers

* Allow untyped pointers for cooperative matrix KHR load and store

Updates to match spec

* Remove extra capability references
* Swap untyped variable data type and storage class operands
* update validation of variables

* update deps

---------

Co-authored-by: David Neto <dneto@google.com>
2024-07-17 14:51:37 -04:00

275 lines
12 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.
#include <unordered_set>
#include <vector>
#include "source/instruction.h"
#include "source/opcode.h"
#include "source/operand.h"
#include "source/val/function.h"
#include "source/val/validate.h"
#include "source/val/validation_state.h"
#include "spirv-tools/libspirv.h"
namespace spvtools {
namespace val {
spv_result_t UpdateIdUse(ValidationState_t& _, const Instruction* inst) {
for (auto& operand : inst->operands()) {
const spv_operand_type_t& type = operand.type;
const uint32_t operand_id = inst->word(operand.offset);
if (spvIsIdType(type) && type != SPV_OPERAND_TYPE_RESULT_ID) {
if (auto def = _.FindDef(operand_id))
def->RegisterUse(inst, operand.offset);
}
}
return SPV_SUCCESS;
}
/// This function checks all ID definitions dominate their use in the CFG.
///
/// This function will iterate over all ID definitions that are defined in the
/// functions of a module and make sure that the definitions appear in a
/// block that dominates their use.
///
/// NOTE: This function does NOT check module scoped functions which are
/// checked during the initial binary parse in the IdPass below
spv_result_t CheckIdDefinitionDominateUse(ValidationState_t& _) {
std::vector<const Instruction*> phi_instructions;
std::unordered_set<uint32_t> phi_ids;
for (const auto& inst : _.ordered_instructions()) {
if (inst.id() == 0) continue;
if (const Function* func = inst.function()) {
if (const BasicBlock* block = inst.block()) {
// If the Id is defined within a block then make sure all references to
// that Id appear in a blocks that are dominated by the defining block
for (auto& use_index_pair : inst.uses()) {
const Instruction* use = use_index_pair.first;
if (const BasicBlock* use_block = use->block()) {
if (use_block->reachable() == false) continue;
if (use->opcode() == spv::Op::OpPhi) {
if (phi_ids.insert(use->id()).second) {
phi_instructions.push_back(use);
}
} else if (!block->dominates(*use->block())) {
return _.diag(SPV_ERROR_INVALID_ID, use_block->label())
<< "ID " << _.getIdName(inst.id()) << " defined in block "
<< _.getIdName(block->id())
<< " does not dominate its use in block "
<< _.getIdName(use_block->id());
}
}
}
} else {
// If the Ids defined within a function but not in a block(i.e. function
// parameters, block ids), then make sure all references to that Id
// appear within the same function
for (auto use : inst.uses()) {
const Instruction* user = use.first;
if (user->function() && user->function() != func) {
return _.diag(SPV_ERROR_INVALID_ID, _.FindDef(func->id()))
<< "ID " << _.getIdName(inst.id()) << " used in function "
<< _.getIdName(user->function()->id())
<< " is used outside of it's defining function "
<< _.getIdName(func->id());
}
}
}
}
// NOTE: Ids defined outside of functions must appear before they are used
// This check is being performed in the IdPass function
}
// Check all OpPhi parent blocks are dominated by the variable's defining
// blocks
for (const Instruction* phi : phi_instructions) {
if (phi->block()->reachable() == false) continue;
for (size_t i = 3; i < phi->operands().size(); i += 2) {
const Instruction* variable = _.FindDef(phi->word(i));
const BasicBlock* parent =
phi->function()->GetBlock(phi->word(i + 1)).first;
if (variable->block() && parent->reachable() &&
!variable->block()->dominates(*parent)) {
return _.diag(SPV_ERROR_INVALID_ID, phi)
<< "In OpPhi instruction " << _.getIdName(phi->id()) << ", ID "
<< _.getIdName(variable->id())
<< " definition does not dominate its parent "
<< _.getIdName(parent->id());
}
}
}
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.
spv_result_t IdPass(ValidationState_t& _, Instruction* inst) {
auto can_have_forward_declared_ids =
spvIsExtendedInstruction(inst->opcode()) &&
spvExtInstIsDebugInfo(inst->ext_inst_type())
? spvDbgInfoExtOperandCanBeForwardDeclaredFunction(
inst->opcode(), inst->ext_inst_type(), inst->word(4))
: spvOperandCanBeForwardDeclaredFunction(inst->opcode());
// Keep track of a result id defined by this instruction. 0 means it
// does not define an id.
uint32_t result_id = 0;
bool has_forward_declared_ids = false;
for (unsigned i = 0; i < inst->operands().size(); i++) {
const spv_parsed_operand_t& operand = inst->operand(i);
const spv_operand_type_t& type = operand.type;
// We only care about Id operands, which are a single word.
const uint32_t operand_word = inst->word(operand.offset);
auto ret = SPV_ERROR_INTERNAL;
switch (type) {
case SPV_OPERAND_TYPE_RESULT_ID:
// NOTE: Multiple Id definitions are being checked by the binary parser.
//
// Defer undefined-forward-reference removal until after we've analyzed
// the remaining operands to this instruction. Deferral only matters
// for OpPhi since it's the only case where it defines its own forward
// reference. Other instructions that can have forward references
// either don't define a value or the forward reference is to a function
// Id (and hence defined outside of a function body).
result_id = operand_word;
// NOTE: The result Id is added (in RegisterInstruction) *after* all of
// the other Ids have been checked to avoid premature use in the same
// instruction.
ret = SPV_SUCCESS;
break;
case SPV_OPERAND_TYPE_ID:
case SPV_OPERAND_TYPE_MEMORY_SEMANTICS_ID:
case SPV_OPERAND_TYPE_SCOPE_ID:
if (const auto def = _.FindDef(operand_word)) {
const auto opcode = inst->opcode();
if (spvOpcodeGeneratesType(def->opcode()) &&
!spvOpcodeGeneratesType(opcode) && !spvOpcodeIsDebug(opcode) &&
!inst->IsDebugInfo() && !inst->IsNonSemantic() &&
!spvOpcodeIsDecoration(opcode) && opcode != spv::Op::OpFunction &&
opcode != spv::Op::OpCooperativeMatrixLengthNV &&
opcode != spv::Op::OpCooperativeMatrixLengthKHR &&
!spvOpcodeGeneratesUntypedPointer(opcode) &&
opcode != spv::Op::OpUntypedArrayLengthKHR &&
!(opcode == spv::Op::OpSpecConstantOp &&
(spv::Op(inst->word(3)) ==
spv::Op::OpCooperativeMatrixLengthNV ||
spv::Op(inst->word(3)) ==
spv::Op::OpCooperativeMatrixLengthKHR))) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Operand " << _.getIdName(operand_word)
<< " cannot be a type";
} else if (def->type_id() == 0 && !spvOpcodeGeneratesType(opcode) &&
!spvOpcodeIsDebug(opcode) && !inst->IsDebugInfo() &&
!inst->IsNonSemantic() && !spvOpcodeIsDecoration(opcode) &&
!spvOpcodeIsBranch(opcode) && opcode != spv::Op::OpPhi &&
opcode != spv::Op::OpExtInst &&
opcode != spv::Op::OpExtInstWithForwardRefsKHR &&
opcode != spv::Op::OpExtInstImport &&
opcode != spv::Op::OpSelectionMerge &&
opcode != spv::Op::OpLoopMerge &&
opcode != spv::Op::OpFunction &&
opcode != spv::Op::OpCooperativeMatrixLengthNV &&
opcode != spv::Op::OpCooperativeMatrixLengthKHR &&
!spvOpcodeGeneratesUntypedPointer(opcode) &&
opcode != spv::Op::OpUntypedArrayLengthKHR &&
!(opcode == spv::Op::OpSpecConstantOp &&
(spv::Op(inst->word(3)) ==
spv::Op::OpCooperativeMatrixLengthNV ||
spv::Op(inst->word(3)) ==
spv::Op::OpCooperativeMatrixLengthKHR))) {
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;
}
} else if (can_have_forward_declared_ids(i)) {
has_forward_declared_ids = true;
if (spvOpcodeGeneratesType(inst->opcode()) &&
!_.IsForwardPointer(operand_word)) {
ret = _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Operand " << _.getIdName(operand_word)
<< " requires a previous definition";
} else {
ret = _.ForwardDeclareId(operand_word);
}
} else {
ret = _.diag(SPV_ERROR_INVALID_ID, inst)
<< "ID " << _.getIdName(operand_word)
<< " has not been defined";
}
break;
case SPV_OPERAND_TYPE_TYPE_ID:
if (_.IsDefinedId(operand_word)) {
auto* def = _.FindDef(operand_word);
if (!spvOpcodeGeneratesType(def->opcode())) {
ret = _.diag(SPV_ERROR_INVALID_ID, inst)
<< "ID " << _.getIdName(operand_word) << " is not a type id";
} else {
ret = SPV_SUCCESS;
}
} else {
ret = _.diag(SPV_ERROR_INVALID_ID, inst)
<< "ID " << _.getIdName(operand_word)
<< " has not been defined";
}
break;
case SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER:
// Ideally, this check would live in validate_extensions.cpp. But since
// forward references are only allowed on non-semantic instructions, and
// ID validation is done first, we would fail with a "ID had not been
// defined" error before we could give a more helpful message. For this
// reason, this test is done here, so we can be more helpful to the
// user.
if (inst->opcode() == spv::Op::OpExtInstWithForwardRefsKHR &&
!inst->IsNonSemantic())
return _.diag(SPV_ERROR_INVALID_DATA, inst)
<< "OpExtInstWithForwardRefsKHR is only allowed with "
"non-semantic instructions.";
ret = SPV_SUCCESS;
break;
default:
ret = SPV_SUCCESS;
break;
}
if (SPV_SUCCESS != ret) return ret;
}
const bool must_have_forward_declared_ids =
inst->opcode() == spv::Op::OpExtInstWithForwardRefsKHR;
if (must_have_forward_declared_ids && !has_forward_declared_ids) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Opcode OpExtInstWithForwardRefsKHR must have at least one "
"forward "
"declared ID.";
}
if (result_id) _.RemoveIfForwardDeclared(result_id);
return SPV_SUCCESS;
}
} // namespace val
} // namespace spvtools