| // Copyright (c) 2018 Google LLC. | |
| // | |
| // 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. | |
| // Validates correctness of barrier SPIR-V instructions. | |
| #include "source/opcode.h" | |
| #include "source/spirv_constant.h" | |
| #include "source/spirv_target_env.h" | |
| #include "source/val/instruction.h" | |
| #include "source/val/validate.h" | |
| #include "source/val/validate_scopes.h" | |
| #include "source/val/validation_state.h" | |
| namespace spvtools { | |
| namespace val { | |
| namespace { | |
| spv_result_t ValidateGroupNonUniformElect(ValidationState_t& _, | |
| const Instruction* inst) { | |
| if (!_.IsBoolScalarType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a boolean scalar type"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformAnyAll(ValidationState_t& _, | |
| const Instruction* inst) { | |
| if (!_.IsBoolScalarType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a boolean scalar type"; | |
| } | |
| if (!_.IsBoolScalarType(_.GetOperandTypeId(inst, 3))) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Predicate must be a boolean scalar type"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformAllEqual(ValidationState_t& _, | |
| const Instruction* inst) { | |
| if (!_.IsBoolScalarType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a boolean scalar type"; | |
| } | |
| const auto value_type = _.GetOperandTypeId(inst, 3); | |
| if (!_.IsFloatScalarOrVectorType(value_type) && | |
| !_.IsIntScalarOrVectorType(value_type) && | |
| !_.IsBoolScalarOrVectorType(value_type)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Value must be a scalar or vector of integer, floating-point, or " | |
| "boolean type"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformBroadcastShuffle(ValidationState_t& _, | |
| const Instruction* inst) { | |
| const auto type_id = inst->type_id(); | |
| if (!_.IsFloatScalarOrVectorType(type_id) && | |
| !_.IsIntScalarOrVectorType(type_id) && | |
| !_.IsBoolScalarOrVectorType(type_id)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a scalar or vector of integer, floating-point, " | |
| "or boolean type"; | |
| } | |
| const auto value_type_id = _.GetOperandTypeId(inst, 3); | |
| if (value_type_id != type_id) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "The type of Value must match the Result type"; | |
| } | |
| const auto GetOperandName = [](const spv::Op opcode) { | |
| std::string operand; | |
| switch (opcode) { | |
| case spv::Op::OpGroupNonUniformBroadcast: | |
| case spv::Op::OpGroupNonUniformShuffle: | |
| operand = "Id"; | |
| break; | |
| case spv::Op::OpGroupNonUniformShuffleXor: | |
| operand = "Mask"; | |
| break; | |
| case spv::Op::OpGroupNonUniformQuadBroadcast: | |
| operand = "Index"; | |
| break; | |
| case spv::Op::OpGroupNonUniformQuadSwap: | |
| operand = "Direction"; | |
| break; | |
| case spv::Op::OpGroupNonUniformShuffleUp: | |
| case spv::Op::OpGroupNonUniformShuffleDown: | |
| default: | |
| operand = "Delta"; | |
| break; | |
| } | |
| return operand; | |
| }; | |
| const auto id_type_id = _.GetOperandTypeId(inst, 4); | |
| if (!_.IsUnsignedIntScalarType(id_type_id)) { | |
| std::string operand = GetOperandName(inst->opcode()); | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << operand << " must be an unsigned integer scalar"; | |
| } | |
| const bool should_be_constant = | |
| inst->opcode() == spv::Op::OpGroupNonUniformQuadSwap || | |
| ((inst->opcode() == spv::Op::OpGroupNonUniformBroadcast || | |
| inst->opcode() == spv::Op::OpGroupNonUniformQuadBroadcast) && | |
| _.version() < SPV_SPIRV_VERSION_WORD(1, 5)); | |
| if (should_be_constant) { | |
| const auto id_id = inst->GetOperandAs<uint32_t>(4); | |
| const auto id_op = _.GetIdOpcode(id_id); | |
| if (!spvOpcodeIsConstant(id_op)) { | |
| std::string operand = GetOperandName(inst->opcode()); | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Before SPIR-V 1.5, " << operand | |
| << " must be a constant instruction"; | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformBroadcastFirst(ValidationState_t& _, | |
| const Instruction* inst) { | |
| const auto type_id = inst->type_id(); | |
| if (!_.IsFloatScalarOrVectorType(type_id) && | |
| !_.IsIntScalarOrVectorType(type_id) && | |
| !_.IsBoolScalarOrVectorType(type_id)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a scalar or vector of integer, floating-point, " | |
| "or boolean type"; | |
| } | |
| const auto value_type_id = _.GetOperandTypeId(inst, 3); | |
| if (value_type_id != type_id) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "The type of Value must match the Result type"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformBallot(ValidationState_t& _, | |
| const Instruction* inst) { | |
| if (!_.IsUnsignedIntVectorType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a 4-component unsigned integer vector"; | |
| } | |
| if (_.GetDimension(inst->type_id()) != 4) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a 4-component unsigned integer vector"; | |
| } | |
| const auto pred_type_id = _.GetOperandTypeId(inst, 3); | |
| if (!_.IsBoolScalarType(pred_type_id)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Predicate must be a boolean scalar"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformInverseBallot(ValidationState_t& _, | |
| const Instruction* inst) { | |
| if (!_.IsBoolScalarType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a boolean scalar"; | |
| } | |
| const auto value_type_id = _.GetOperandTypeId(inst, 3); | |
| if (!_.IsUnsignedIntVectorType(value_type_id)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Value must be a 4-component unsigned integer vector"; | |
| } | |
| if (_.GetDimension(value_type_id) != 4) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Value must be a 4-component unsigned integer vector"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformBallotBitExtract(ValidationState_t& _, | |
| const Instruction* inst) { | |
| if (!_.IsBoolScalarType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a boolean scalar"; | |
| } | |
| const auto value_type_id = _.GetOperandTypeId(inst, 3); | |
| if (!_.IsUnsignedIntVectorType(value_type_id)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Value must be a 4-component unsigned integer vector"; | |
| } | |
| if (_.GetDimension(value_type_id) != 4) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Value must be a 4-component unsigned integer vector"; | |
| } | |
| const auto id_type_id = _.GetOperandTypeId(inst, 4); | |
| if (!_.IsUnsignedIntScalarType(id_type_id)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Id must be an unsigned integer scalar"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformBallotBitCount(ValidationState_t& _, | |
| const Instruction* inst) { | |
| // Scope is already checked by ValidateExecutionScope() above. | |
| const uint32_t result_type = inst->type_id(); | |
| if (!_.IsUnsignedIntScalarType(result_type)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Expected Result Type to be an unsigned integer type scalar."; | |
| } | |
| const auto value = inst->GetOperandAs<uint32_t>(4); | |
| const auto value_type = _.FindDef(value)->type_id(); | |
| if (!_.IsUnsignedIntVectorType(value_type) || | |
| _.GetDimension(value_type) != 4) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Expected Value to be a " | |
| "vector of four components " | |
| "of integer type scalar"; | |
| } | |
| const auto group = inst->GetOperandAs<spv::GroupOperation>(3); | |
| if (spvIsVulkanEnv(_.context()->target_env)) { | |
| if ((group != spv::GroupOperation::Reduce) && | |
| (group != spv::GroupOperation::InclusiveScan) && | |
| (group != spv::GroupOperation::ExclusiveScan)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << _.VkErrorID(4685) | |
| << "In Vulkan: The OpGroupNonUniformBallotBitCount group " | |
| "operation must be only: Reduce, InclusiveScan, or " | |
| "ExclusiveScan."; | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformBallotFind(ValidationState_t& _, | |
| const Instruction* inst) { | |
| if (!_.IsUnsignedIntScalarType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be an unsigned integer scalar"; | |
| } | |
| const auto value_type_id = _.GetOperandTypeId(inst, 3); | |
| if (!_.IsUnsignedIntVectorType(value_type_id)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Value must be a 4-component unsigned integer vector"; | |
| } | |
| if (_.GetDimension(value_type_id) != 4) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Value must be a 4-component unsigned integer vector"; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformArithmetic(ValidationState_t& _, | |
| const Instruction* inst) { | |
| const bool is_unsigned = inst->opcode() == spv::Op::OpGroupNonUniformUMin || | |
| inst->opcode() == spv::Op::OpGroupNonUniformUMax; | |
| const bool is_float = inst->opcode() == spv::Op::OpGroupNonUniformFAdd || | |
| inst->opcode() == spv::Op::OpGroupNonUniformFMul || | |
| inst->opcode() == spv::Op::OpGroupNonUniformFMin || | |
| inst->opcode() == spv::Op::OpGroupNonUniformFMax; | |
| const bool is_bool = inst->opcode() == spv::Op::OpGroupNonUniformLogicalAnd || | |
| inst->opcode() == spv::Op::OpGroupNonUniformLogicalOr || | |
| inst->opcode() == spv::Op::OpGroupNonUniformLogicalXor; | |
| if (is_float) { | |
| if (!_.IsFloatScalarOrVectorType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a floating-point scalar or vector"; | |
| } | |
| } else if (is_bool) { | |
| if (!_.IsBoolScalarOrVectorType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be a boolean scalar or vector"; | |
| } | |
| } else if (is_unsigned) { | |
| if (!_.IsUnsignedIntScalarOrVectorType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be an unsigned integer scalar or vector"; | |
| } | |
| } else if (!_.IsIntScalarOrVectorType(inst->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result must be an integer scalar or vector"; | |
| } | |
| const auto value_type_id = _.GetOperandTypeId(inst, 4); | |
| if (value_type_id != inst->type_id()) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "The type of Value must match the Result type"; | |
| } | |
| const auto group_op = inst->GetOperandAs<spv::GroupOperation>(3); | |
| bool is_clustered_reduce = group_op == spv::GroupOperation::ClusteredReduce; | |
| bool is_partitioned_nv = | |
| group_op == spv::GroupOperation::PartitionedReduceNV || | |
| group_op == spv::GroupOperation::PartitionedInclusiveScanNV || | |
| group_op == spv::GroupOperation::PartitionedExclusiveScanNV; | |
| if (inst->operands().size() <= 5) { | |
| if (is_clustered_reduce) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "ClusterSize must be present when Operation is ClusteredReduce"; | |
| } else if (is_partitioned_nv) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Ballot must be present when Operation is PartitionedReduceNV, " | |
| "PartitionedInclusiveScanNV, or PartitionedExclusiveScanNV"; | |
| } | |
| } else { | |
| const auto operand_id = inst->GetOperandAs<uint32_t>(5); | |
| const auto* operand = _.FindDef(operand_id); | |
| if (is_partitioned_nv) { | |
| if (!operand || !_.IsIntScalarOrVectorType(operand->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Ballot must be a 4-component integer vector"; | |
| } | |
| if (_.GetDimension(operand->type_id()) != 4) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Ballot must be a 4-component integer vector"; | |
| } | |
| } else { | |
| if (!operand || !_.IsUnsignedIntScalarType(operand->type_id())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "ClusterSize must be an unsigned integer scalar"; | |
| } | |
| if (!spvOpcodeIsConstant(operand->opcode())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "ClusterSize must be a constant instruction"; | |
| } | |
| } | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| spv_result_t ValidateGroupNonUniformRotateKHR(ValidationState_t& _, | |
| const Instruction* inst) { | |
| // Scope is already checked by ValidateExecutionScope() above. | |
| const uint32_t result_type = inst->type_id(); | |
| if (!_.IsIntScalarOrVectorType(result_type) && | |
| !_.IsFloatScalarOrVectorType(result_type) && | |
| !_.IsBoolScalarOrVectorType(result_type)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Expected Result Type to be a scalar or vector of " | |
| "floating-point, integer or boolean type."; | |
| } | |
| const uint32_t value_type = _.GetTypeId(inst->GetOperandAs<uint32_t>(3)); | |
| if (value_type != result_type) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Result Type must be the same as the type of Value."; | |
| } | |
| const uint32_t delta_type = _.GetTypeId(inst->GetOperandAs<uint32_t>(4)); | |
| if (!_.IsUnsignedIntScalarType(delta_type)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "Delta must be a scalar of integer type, whose Signedness " | |
| "operand is 0."; | |
| } | |
| if (inst->words().size() > 6) { | |
| const uint32_t cluster_size_op_id = inst->GetOperandAs<uint32_t>(5); | |
| const Instruction* cluster_size_inst = _.FindDef(cluster_size_op_id); | |
| const uint32_t cluster_size_type = | |
| cluster_size_inst ? cluster_size_inst->type_id() : 0; | |
| if (!_.IsUnsignedIntScalarType(cluster_size_type)) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "ClusterSize must be a scalar of integer type, whose " | |
| "Signedness operand is 0."; | |
| } | |
| if (!spvOpcodeIsConstant(cluster_size_inst->opcode())) { | |
| return _.diag(SPV_ERROR_INVALID_DATA, inst) | |
| << "ClusterSize must come from a constant instruction."; | |
| } | |
| uint64_t cluster_size; | |
| const bool valid_const = | |
| _.EvalConstantValUint64(cluster_size_op_id, &cluster_size); | |
| if (valid_const && | |
| ((cluster_size == 0) || ((cluster_size & (cluster_size - 1)) != 0))) { | |
| return _.diag(SPV_WARNING, inst) | |
| << "Behavior is undefined unless ClusterSize is at least 1 and a " | |
| "power of 2."; | |
| } | |
| // TODO(kpet) Warn about undefined behavior when ClusterSize is greater than | |
| // the declared SubGroupSize | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| } // namespace | |
| // Validates correctness of non-uniform group instructions. | |
| spv_result_t NonUniformPass(ValidationState_t& _, const Instruction* inst) { | |
| const spv::Op opcode = inst->opcode(); | |
| if (spvOpcodeIsNonUniformGroupOperation(opcode)) { | |
| // OpGroupNonUniformQuadAllKHR and OpGroupNonUniformQuadAnyKHR don't have | |
| // scope paramter | |
| if ((opcode != spv::Op::OpGroupNonUniformQuadAllKHR) && | |
| (opcode != spv::Op::OpGroupNonUniformQuadAnyKHR)) { | |
| const uint32_t execution_scope = inst->GetOperandAs<uint32_t>(2); | |
| if (auto error = ValidateExecutionScope(_, inst, execution_scope)) { | |
| return error; | |
| } | |
| } | |
| } | |
| switch (opcode) { | |
| case spv::Op::OpGroupNonUniformElect: | |
| return ValidateGroupNonUniformElect(_, inst); | |
| case spv::Op::OpGroupNonUniformAny: | |
| case spv::Op::OpGroupNonUniformAll: | |
| return ValidateGroupNonUniformAnyAll(_, inst); | |
| case spv::Op::OpGroupNonUniformAllEqual: | |
| return ValidateGroupNonUniformAllEqual(_, inst); | |
| case spv::Op::OpGroupNonUniformBroadcast: | |
| case spv::Op::OpGroupNonUniformShuffle: | |
| case spv::Op::OpGroupNonUniformShuffleXor: | |
| case spv::Op::OpGroupNonUniformShuffleUp: | |
| case spv::Op::OpGroupNonUniformShuffleDown: | |
| case spv::Op::OpGroupNonUniformQuadBroadcast: | |
| case spv::Op::OpGroupNonUniformQuadSwap: | |
| return ValidateGroupNonUniformBroadcastShuffle(_, inst); | |
| case spv::Op::OpGroupNonUniformBroadcastFirst: | |
| return ValidateGroupNonUniformBroadcastFirst(_, inst); | |
| case spv::Op::OpGroupNonUniformBallot: | |
| return ValidateGroupNonUniformBallot(_, inst); | |
| case spv::Op::OpGroupNonUniformInverseBallot: | |
| return ValidateGroupNonUniformInverseBallot(_, inst); | |
| case spv::Op::OpGroupNonUniformBallotBitExtract: | |
| return ValidateGroupNonUniformBallotBitExtract(_, inst); | |
| case spv::Op::OpGroupNonUniformBallotBitCount: | |
| return ValidateGroupNonUniformBallotBitCount(_, inst); | |
| case spv::Op::OpGroupNonUniformBallotFindLSB: | |
| case spv::Op::OpGroupNonUniformBallotFindMSB: | |
| return ValidateGroupNonUniformBallotFind(_, inst); | |
| case spv::Op::OpGroupNonUniformIAdd: | |
| case spv::Op::OpGroupNonUniformFAdd: | |
| case spv::Op::OpGroupNonUniformIMul: | |
| case spv::Op::OpGroupNonUniformFMul: | |
| case spv::Op::OpGroupNonUniformSMin: | |
| case spv::Op::OpGroupNonUniformUMin: | |
| case spv::Op::OpGroupNonUniformFMin: | |
| case spv::Op::OpGroupNonUniformSMax: | |
| case spv::Op::OpGroupNonUniformUMax: | |
| case spv::Op::OpGroupNonUniformFMax: | |
| case spv::Op::OpGroupNonUniformBitwiseAnd: | |
| case spv::Op::OpGroupNonUniformBitwiseOr: | |
| case spv::Op::OpGroupNonUniformBitwiseXor: | |
| case spv::Op::OpGroupNonUniformLogicalAnd: | |
| case spv::Op::OpGroupNonUniformLogicalOr: | |
| case spv::Op::OpGroupNonUniformLogicalXor: | |
| return ValidateGroupNonUniformArithmetic(_, inst); | |
| case spv::Op::OpGroupNonUniformRotateKHR: | |
| return ValidateGroupNonUniformRotateKHR(_, inst); | |
| default: | |
| break; | |
| } | |
| return SPV_SUCCESS; | |
| } | |
| } // namespace val | |
| } // namespace spvtools |