// Copyright (c) 2026 LunarG 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. // Validates correctness of Pipe SPIR-V instructions. #include "source/val/instruction.h" #include "source/val/validate.h" #include "source/val/validate_scopes.h" #include "source/val/validation_state.h" #include "spirv/unified1/spirv.hpp11" namespace spvtools { namespace val { namespace { enum class ValidPipeType { READ_ONLY, WRITE_ONLY, READ_OR_WRITE, // still excludes Read AND Write }; spv_result_t ValidatePipeType(ValidationState_t& _, const Instruction* inst, uint32_t operand, ValidPipeType valid_pt) { const Instruction* pipe_type = _.FindDef(_.GetOperandTypeId(inst, operand)); if (pipe_type->opcode() != spv::Op::OpTypePipe) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Pipe must be a type of OpTypePipe."; } const auto access_qualifier = pipe_type->GetOperandAs(1); if (valid_pt == ValidPipeType::READ_ONLY) { if (access_qualifier != spv::AccessQualifier::ReadOnly) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Pipe must have a OpTypePipe with ReadOnly access qualifier."; } } else if (valid_pt == ValidPipeType::WRITE_ONLY) { if (access_qualifier != spv::AccessQualifier::WriteOnly) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Pipe must have a OpTypePipe with WriteOnly access qualifier."; } } else if (valid_pt == ValidPipeType::READ_OR_WRITE) { if (access_qualifier != spv::AccessQualifier::ReadOnly && access_qualifier != spv::AccessQualifier::WriteOnly) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Pipe must have a OpTypePipe with ReadOnly or WriteOnly access " "qualifier."; } } return SPV_SUCCESS; } spv_result_t ValidatePacketSizeAlign(ValidationState_t& _, const Instruction* inst, uint32_t size_operand, uint32_t alignment_operand) { const uint32_t packet_size_id = _.GetOperandTypeId(inst, size_operand); if (!_.IsIntScalarType(packet_size_id, 32)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Packet Size must be a 32-bit scalar integer."; } const uint32_t packet_alignment_id = _.GetOperandTypeId(inst, alignment_operand); if (!_.IsIntScalarType(packet_alignment_id, 32)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Packet Alignment must be a 32-bit scalar integer."; } return SPV_SUCCESS; } spv_result_t ValidateReadWritePipe(ValidationState_t& _, const Instruction* inst) { const uint32_t result_type = inst->type_id(); if (!_.IsIntScalarType(result_type, 32)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Result Type must be a 32-bit int scalar."; } if (inst->opcode() == spv::Op::OpReadPipe) { if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY)) return error; } else if (inst->opcode() == spv::Op::OpWritePipe) { if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY)) return error; } const Instruction* pointer_type = _.FindDef(_.GetOperandTypeId(inst, 3)); if (pointer_type->opcode() != spv::Op::OpTypePointer) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Pointer must be a type of OpTypePointer."; } if (pointer_type->GetOperandAs(1) != spv::StorageClass::Generic) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Pointer must be a OpTypePointer with a Generic storage class."; } if (auto error = ValidatePacketSizeAlign(_, inst, 4, 5)) return error; return SPV_SUCCESS; } spv_result_t ValidateReservedReadWritePipe(ValidationState_t& _, const Instruction* inst) { const uint32_t result_type = inst->type_id(); if (!_.IsIntScalarType(result_type, 32)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Result Type must be a 32-bit int scalar."; } if (inst->opcode() == spv::Op::OpReservedReadPipe) { if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY)) return error; } else if (inst->opcode() == spv::Op::OpReservedWritePipe) { if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY)) return error; } const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 3)); if (reserve_id->opcode() != spv::Op::OpTypeReserveId) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Reserve Id type must be OpTypeReserveId."; } const uint32_t index_id = _.GetOperandTypeId(inst, 4); if (!_.IsIntScalarType(index_id, 32)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Index must be a 32-bit scalar integer."; } const Instruction* pointer_type = _.FindDef(_.GetOperandTypeId(inst, 5)); if (pointer_type->opcode() != spv::Op::OpTypePointer) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Pointer must be a type of OpTypePointer."; } if (pointer_type->GetOperandAs(1) != spv::StorageClass::Generic) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Pointer must be a OpTypePointer with a Generic storage class."; } if (auto error = ValidatePacketSizeAlign(_, inst, 6, 7)) return error; return SPV_SUCCESS; } spv_result_t ValidateReservePackets(ValidationState_t& _, const Instruction* inst) { const Instruction* result_type = _.FindDef(inst->type_id()); if (result_type->opcode() != spv::Op::OpTypeReserveId) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Result Type must be OpTypeReserveId."; } if (inst->opcode() == spv::Op::OpReserveReadPipePackets) { if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_ONLY)) return error; } else if (inst->opcode() == spv::Op::OpReserveWritePipePackets) { if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::WRITE_ONLY)) return error; } const uint32_t num_packets_id = _.GetOperandTypeId(inst, 3); if (!_.IsIntScalarType(num_packets_id, 32)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Num Packets must be a 32-bit scalar integer."; } if (auto error = ValidatePacketSizeAlign(_, inst, 4, 5)) return error; return SPV_SUCCESS; } spv_result_t ValidateGroupReservePackets(ValidationState_t& _, const Instruction* inst) { const Instruction* result_type = _.FindDef(inst->type_id()); if (result_type->opcode() != spv::Op::OpTypeReserveId) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Result Type must be OpTypeReserveId."; } if (inst->opcode() == spv::Op::OpGroupReserveReadPipePackets) { if (auto error = ValidatePipeType(_, inst, 3, ValidPipeType::READ_ONLY)) return error; } else if (inst->opcode() == spv::Op::OpGroupReserveWritePipePackets) { if (auto error = ValidatePipeType(_, inst, 3, ValidPipeType::WRITE_ONLY)) return error; } const uint32_t num_packets_id = _.GetOperandTypeId(inst, 4); if (!_.IsIntScalarType(num_packets_id, 32)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Num Packets must be a 32-bit scalar integer."; } if (auto error = ValidatePacketSizeAlign(_, inst, 5, 6)) return error; return SPV_SUCCESS; } spv_result_t ValidateCommitPipe(ValidationState_t& _, const Instruction* inst) { if (inst->opcode() == spv::Op::OpCommitReadPipe) { if (auto error = ValidatePipeType(_, inst, 0, ValidPipeType::READ_ONLY)) return error; } else if (inst->opcode() == spv::Op::OpCommitWritePipe) { if (auto error = ValidatePipeType(_, inst, 0, ValidPipeType::WRITE_ONLY)) return error; } const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 1)); if (reserve_id->opcode() != spv::Op::OpTypeReserveId) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Reserve Id type must be OpTypeReserveId."; } if (auto error = ValidatePacketSizeAlign(_, inst, 2, 3)) return error; return SPV_SUCCESS; } spv_result_t ValidateGroupCommitPipe(ValidationState_t& _, const Instruction* inst) { if (inst->opcode() == spv::Op::OpGroupCommitReadPipe) { if (auto error = ValidatePipeType(_, inst, 1, ValidPipeType::READ_ONLY)) return error; } else if (inst->opcode() == spv::Op::OpGroupCommitWritePipe) { if (auto error = ValidatePipeType(_, inst, 1, ValidPipeType::WRITE_ONLY)) return error; } const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 2)); if (reserve_id->opcode() != spv::Op::OpTypeReserveId) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Reserve Id type must be OpTypeReserveId."; } if (auto error = ValidatePacketSizeAlign(_, inst, 3, 4)) return error; return SPV_SUCCESS; } spv_result_t ValidatePipePacketsQuery(ValidationState_t& _, const Instruction* inst) { const uint32_t result_type = inst->type_id(); if (!_.IsIntScalarType(result_type, 32)) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Result Type must be a 32-bit int scalar."; } if (auto error = ValidatePipeType(_, inst, 2, ValidPipeType::READ_OR_WRITE)) return error; if (auto error = ValidatePacketSizeAlign(_, inst, 3, 4)) return error; return SPV_SUCCESS; } spv_result_t ValidateIsValidReserveId(ValidationState_t& _, const Instruction* inst) { if (!_.IsBoolScalarType(inst->type_id())) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Result Type must be a bool scalar"; } const Instruction* reserve_id = _.FindDef(_.GetOperandTypeId(inst, 2)); if (reserve_id->opcode() != spv::Op::OpTypeReserveId) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Reserve Id type must be OpTypeReserveId."; } return SPV_SUCCESS; } spv_result_t ValidateCreatePipeFromPipeStorage(ValidationState_t& _, const Instruction* inst) { const Instruction* result_type = _.FindDef(inst->type_id()); if (result_type->opcode() != spv::Op::OpTypePipe) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Result Type must be OpTypePipe."; } // TODO - Need to check OpTypeStorage is from OpConstantPipeStorage return SPV_SUCCESS; } spv_result_t ValidateConstantPipeStorage(ValidationState_t& _, const Instruction* inst) { const Instruction* result_type = _.FindDef(inst->type_id()); if (result_type->opcode() != spv::Op::OpTypePipeStorage) { return _.diag(SPV_ERROR_INVALID_DATA, inst) << "Result Type must be OpTypePipeStorage."; } // TODO - Should we validate the literal values? // https://gitlab.khronos.org/spirv/SPIR-V/-/issues/914 return SPV_SUCCESS; } } // namespace // Validates correctness of pipe instructions. spv_result_t PipePass(ValidationState_t& _, const Instruction* inst) { switch (inst->opcode()) { case spv::Op::OpReadPipe: case spv::Op::OpWritePipe: return ValidateReadWritePipe(_, inst); case spv::Op::OpReservedReadPipe: case spv::Op::OpReservedWritePipe: return ValidateReservedReadWritePipe(_, inst); case spv::Op::OpReserveReadPipePackets: case spv::Op::OpReserveWritePipePackets: return ValidateReservePackets(_, inst); case spv::Op::OpGroupReserveReadPipePackets: case spv::Op::OpGroupReserveWritePipePackets: return ValidateGroupReservePackets(_, inst); case spv::Op::OpCommitReadPipe: case spv::Op::OpCommitWritePipe: return ValidateCommitPipe(_, inst); case spv::Op::OpGroupCommitReadPipe: case spv::Op::OpGroupCommitWritePipe: return ValidateGroupCommitPipe(_, inst); case spv::Op::OpGetNumPipePackets: case spv::Op::OpGetMaxPipePackets: return ValidatePipePacketsQuery(_, inst); case spv::Op::OpIsValidReserveId: return ValidateIsValidReserveId(_, inst); case spv::Op::OpCreatePipeFromPipeStorage: return ValidateCreatePipeFromPipeStorage(_, inst); case spv::Op::OpConstantPipeStorage: return ValidateConstantPipeStorage(_, inst); default: break; } return SPV_SUCCESS; } } // namespace val } // namespace spvtools