| 1 | // Copyright (c) 2017 Google Inc. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | // Validates OpCapability instruction. |
| 16 | |
| 17 | #include "source/val/validate.h" |
| 18 | |
| 19 | #include <cassert> |
| 20 | #include <string> |
| 21 | #include <unordered_set> |
| 22 | |
| 23 | #include "source/diagnostic.h" |
| 24 | #include "source/opcode.h" |
| 25 | #include "source/val/instruction.h" |
| 26 | #include "source/val/validation_state.h" |
| 27 | |
| 28 | namespace spvtools { |
| 29 | namespace val { |
| 30 | namespace { |
| 31 | |
| 32 | bool IsSupportGuaranteedVulkan_1_0(uint32_t capability) { |
| 33 | switch (capability) { |
| 34 | case SpvCapabilityMatrix: |
| 35 | case SpvCapabilityShader: |
| 36 | case SpvCapabilityInputAttachment: |
| 37 | case SpvCapabilitySampled1D: |
| 38 | case SpvCapabilityImage1D: |
| 39 | case SpvCapabilitySampledBuffer: |
| 40 | case SpvCapabilityImageBuffer: |
| 41 | case SpvCapabilityImageQuery: |
| 42 | case SpvCapabilityDerivativeControl: |
| 43 | return true; |
| 44 | } |
| 45 | return false; |
| 46 | } |
| 47 | |
| 48 | bool IsSupportGuaranteedVulkan_1_1(uint32_t capability) { |
| 49 | if (IsSupportGuaranteedVulkan_1_0(capability)) return true; |
| 50 | switch (capability) { |
| 51 | case SpvCapabilityDeviceGroup: |
| 52 | case SpvCapabilityMultiView: |
| 53 | return true; |
| 54 | } |
| 55 | return false; |
| 56 | } |
| 57 | |
| 58 | bool IsSupportGuaranteedVulkan_1_2(uint32_t capability) { |
| 59 | if (IsSupportGuaranteedVulkan_1_1(capability)) return true; |
| 60 | switch (capability) { |
| 61 | case SpvCapabilityShaderNonUniform: |
| 62 | return true; |
| 63 | } |
| 64 | return false; |
| 65 | } |
| 66 | |
| 67 | bool IsSupportOptionalVulkan_1_0(uint32_t capability) { |
| 68 | switch (capability) { |
| 69 | case SpvCapabilityGeometry: |
| 70 | case SpvCapabilityTessellation: |
| 71 | case SpvCapabilityFloat64: |
| 72 | case SpvCapabilityInt64: |
| 73 | case SpvCapabilityInt16: |
| 74 | case SpvCapabilityTessellationPointSize: |
| 75 | case SpvCapabilityGeometryPointSize: |
| 76 | case SpvCapabilityImageGatherExtended: |
| 77 | case SpvCapabilityStorageImageMultisample: |
| 78 | case SpvCapabilityUniformBufferArrayDynamicIndexing: |
| 79 | case SpvCapabilitySampledImageArrayDynamicIndexing: |
| 80 | case SpvCapabilityStorageBufferArrayDynamicIndexing: |
| 81 | case SpvCapabilityStorageImageArrayDynamicIndexing: |
| 82 | case SpvCapabilityClipDistance: |
| 83 | case SpvCapabilityCullDistance: |
| 84 | case SpvCapabilityImageCubeArray: |
| 85 | case SpvCapabilitySampleRateShading: |
| 86 | case SpvCapabilitySparseResidency: |
| 87 | case SpvCapabilityMinLod: |
| 88 | case SpvCapabilitySampledCubeArray: |
| 89 | case SpvCapabilityImageMSArray: |
| 90 | case SpvCapabilityStorageImageExtendedFormats: |
| 91 | case SpvCapabilityInterpolationFunction: |
| 92 | case SpvCapabilityStorageImageReadWithoutFormat: |
| 93 | case SpvCapabilityStorageImageWriteWithoutFormat: |
| 94 | case SpvCapabilityMultiViewport: |
| 95 | case SpvCapabilityInt64Atomics: |
| 96 | case SpvCapabilityTransformFeedback: |
| 97 | case SpvCapabilityGeometryStreams: |
| 98 | case SpvCapabilityFloat16: |
| 99 | case SpvCapabilityInt8: |
| 100 | return true; |
| 101 | } |
| 102 | return false; |
| 103 | } |
| 104 | |
| 105 | bool IsSupportOptionalVulkan_1_1(uint32_t capability) { |
| 106 | if (IsSupportOptionalVulkan_1_0(capability)) return true; |
| 107 | |
| 108 | switch (capability) { |
| 109 | case SpvCapabilityGroupNonUniform: |
| 110 | case SpvCapabilityGroupNonUniformVote: |
| 111 | case SpvCapabilityGroupNonUniformArithmetic: |
| 112 | case SpvCapabilityGroupNonUniformBallot: |
| 113 | case SpvCapabilityGroupNonUniformShuffle: |
| 114 | case SpvCapabilityGroupNonUniformShuffleRelative: |
| 115 | case SpvCapabilityGroupNonUniformClustered: |
| 116 | case SpvCapabilityGroupNonUniformQuad: |
| 117 | case SpvCapabilityDrawParameters: |
| 118 | // Alias SpvCapabilityStorageBuffer16BitAccess. |
| 119 | case SpvCapabilityStorageUniformBufferBlock16: |
| 120 | // Alias SpvCapabilityUniformAndStorageBuffer16BitAccess. |
| 121 | case SpvCapabilityStorageUniform16: |
| 122 | case SpvCapabilityStoragePushConstant16: |
| 123 | case SpvCapabilityStorageInputOutput16: |
| 124 | case SpvCapabilityDeviceGroup: |
| 125 | case SpvCapabilityMultiView: |
| 126 | case SpvCapabilityVariablePointersStorageBuffer: |
| 127 | case SpvCapabilityVariablePointers: |
| 128 | return true; |
| 129 | } |
| 130 | return false; |
| 131 | } |
| 132 | |
| 133 | bool IsSupportOptionalVulkan_1_2(uint32_t capability) { |
| 134 | if (IsSupportOptionalVulkan_1_1(capability)) return true; |
| 135 | |
| 136 | switch (capability) { |
| 137 | case SpvCapabilityDenormPreserve: |
| 138 | case SpvCapabilityDenormFlushToZero: |
| 139 | case SpvCapabilitySignedZeroInfNanPreserve: |
| 140 | case SpvCapabilityRoundingModeRTE: |
| 141 | case SpvCapabilityRoundingModeRTZ: |
| 142 | case SpvCapabilityVulkanMemoryModel: |
| 143 | case SpvCapabilityVulkanMemoryModelDeviceScope: |
| 144 | case SpvCapabilityStorageBuffer8BitAccess: |
| 145 | case SpvCapabilityUniformAndStorageBuffer8BitAccess: |
| 146 | case SpvCapabilityStoragePushConstant8: |
| 147 | case SpvCapabilityShaderViewportIndex: |
| 148 | case SpvCapabilityShaderLayer: |
| 149 | case SpvCapabilityPhysicalStorageBufferAddresses: |
| 150 | case SpvCapabilityRuntimeDescriptorArray: |
| 151 | case SpvCapabilityUniformTexelBufferArrayDynamicIndexing: |
| 152 | case SpvCapabilityStorageTexelBufferArrayDynamicIndexing: |
| 153 | case SpvCapabilityUniformBufferArrayNonUniformIndexing: |
| 154 | case SpvCapabilitySampledImageArrayNonUniformIndexing: |
| 155 | case SpvCapabilityStorageBufferArrayNonUniformIndexing: |
| 156 | case SpvCapabilityStorageImageArrayNonUniformIndexing: |
| 157 | case SpvCapabilityInputAttachmentArrayNonUniformIndexing: |
| 158 | case SpvCapabilityUniformTexelBufferArrayNonUniformIndexing: |
| 159 | case SpvCapabilityStorageTexelBufferArrayNonUniformIndexing: |
| 160 | return true; |
| 161 | } |
| 162 | return false; |
| 163 | } |
| 164 | |
| 165 | bool IsSupportGuaranteedOpenCL_1_2(uint32_t capability, bool embedded_profile) { |
| 166 | switch (capability) { |
| 167 | case SpvCapabilityAddresses: |
| 168 | case SpvCapabilityFloat16Buffer: |
| 169 | case SpvCapabilityGroups: |
| 170 | case SpvCapabilityInt16: |
| 171 | case SpvCapabilityInt8: |
| 172 | case SpvCapabilityKernel: |
| 173 | case SpvCapabilityLinkage: |
| 174 | case SpvCapabilityVector16: |
| 175 | return true; |
| 176 | case SpvCapabilityInt64: |
| 177 | return !embedded_profile; |
| 178 | case SpvCapabilityPipes: |
| 179 | return embedded_profile; |
| 180 | } |
| 181 | return false; |
| 182 | } |
| 183 | |
| 184 | bool IsSupportGuaranteedOpenCL_2_0(uint32_t capability, bool embedded_profile) { |
| 185 | if (IsSupportGuaranteedOpenCL_1_2(capability, embedded_profile)) return true; |
| 186 | |
| 187 | switch (capability) { |
| 188 | case SpvCapabilityDeviceEnqueue: |
| 189 | case SpvCapabilityGenericPointer: |
| 190 | case SpvCapabilityPipes: |
| 191 | return true; |
| 192 | } |
| 193 | return false; |
| 194 | } |
| 195 | |
| 196 | bool IsSupportGuaranteedOpenCL_2_2(uint32_t capability, bool embedded_profile) { |
| 197 | if (IsSupportGuaranteedOpenCL_2_0(capability, embedded_profile)) return true; |
| 198 | |
| 199 | switch (capability) { |
| 200 | case SpvCapabilitySubgroupDispatch: |
| 201 | case SpvCapabilityPipeStorage: |
| 202 | return true; |
| 203 | } |
| 204 | return false; |
| 205 | } |
| 206 | |
| 207 | bool IsSupportOptionalOpenCL_1_2(uint32_t capability) { |
| 208 | switch (capability) { |
| 209 | case SpvCapabilityImageBasic: |
| 210 | case SpvCapabilityFloat64: |
| 211 | return true; |
| 212 | } |
| 213 | return false; |
| 214 | } |
| 215 | |
| 216 | // Checks if |capability| was enabled by extension. |
| 217 | bool IsEnabledByExtension(ValidationState_t& _, uint32_t capability) { |
| 218 | spv_operand_desc operand_desc = nullptr; |
| 219 | _.grammar().lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, |
| 220 | &operand_desc); |
| 221 | |
| 222 | // operand_desc is expected to be not null, otherwise validator would have |
| 223 | // failed at an earlier stage. This 'assert' is 'just in case'. |
| 224 | assert(operand_desc); |
| 225 | |
| 226 | ExtensionSet operand_exts(operand_desc->numExtensions, |
| 227 | operand_desc->extensions); |
| 228 | if (operand_exts.IsEmpty()) return false; |
| 229 | |
| 230 | return _.HasAnyOfExtensions(operand_exts); |
| 231 | } |
| 232 | |
| 233 | bool IsEnabledByCapabilityOpenCL_1_2(ValidationState_t& _, |
| 234 | uint32_t capability) { |
| 235 | if (_.HasCapability(SpvCapabilityImageBasic)) { |
| 236 | switch (capability) { |
| 237 | case SpvCapabilityLiteralSampler: |
| 238 | case SpvCapabilitySampled1D: |
| 239 | case SpvCapabilityImage1D: |
| 240 | case SpvCapabilitySampledBuffer: |
| 241 | case SpvCapabilityImageBuffer: |
| 242 | return true; |
| 243 | } |
| 244 | return false; |
| 245 | } |
| 246 | return false; |
| 247 | } |
| 248 | |
| 249 | bool IsEnabledByCapabilityOpenCL_2_0(ValidationState_t& _, |
| 250 | uint32_t capability) { |
| 251 | if (_.HasCapability(SpvCapabilityImageBasic)) { |
| 252 | switch (capability) { |
| 253 | case SpvCapabilityImageReadWrite: |
| 254 | case SpvCapabilityLiteralSampler: |
| 255 | case SpvCapabilitySampled1D: |
| 256 | case SpvCapabilityImage1D: |
| 257 | case SpvCapabilitySampledBuffer: |
| 258 | case SpvCapabilityImageBuffer: |
| 259 | return true; |
| 260 | } |
| 261 | return false; |
| 262 | } |
| 263 | return false; |
| 264 | } |
| 265 | |
| 266 | bool IsSupportGuaranteedWebGPU(uint32_t capability) { |
| 267 | switch (capability) { |
| 268 | case SpvCapabilityMatrix: |
| 269 | case SpvCapabilityShader: |
| 270 | case SpvCapabilitySampled1D: |
| 271 | case SpvCapabilityImage1D: |
| 272 | case SpvCapabilityDerivativeControl: |
| 273 | case SpvCapabilityImageQuery: |
| 274 | return true; |
| 275 | } |
| 276 | return false; |
| 277 | } |
| 278 | |
| 279 | } // namespace |
| 280 | |
| 281 | // Validates that capability declarations use operands allowed in the current |
| 282 | // context. |
| 283 | spv_result_t CapabilityPass(ValidationState_t& _, const Instruction* inst) { |
| 284 | if (inst->opcode() != SpvOpCapability) return SPV_SUCCESS; |
| 285 | |
| 286 | assert(inst->operands().size() == 1); |
| 287 | |
| 288 | const spv_parsed_operand_t& operand = inst->operand(0); |
| 289 | |
| 290 | assert(operand.num_words == 1); |
| 291 | assert(operand.offset < inst->words().size()); |
| 292 | |
| 293 | const uint32_t capability = inst->word(operand.offset); |
| 294 | const auto capability_str = [&_, capability]() { |
| 295 | spv_operand_desc desc = nullptr; |
| 296 | if (_.grammar().lookupOperand(SPV_OPERAND_TYPE_CAPABILITY, capability, |
| 297 | &desc) != SPV_SUCCESS || |
| 298 | !desc) { |
| 299 | return std::string("Unknown" ); |
| 300 | } |
| 301 | return std::string(desc->name); |
| 302 | }; |
| 303 | |
| 304 | const auto env = _.context()->target_env; |
| 305 | const bool opencl_embedded = env == SPV_ENV_OPENCL_EMBEDDED_1_2 || |
| 306 | env == SPV_ENV_OPENCL_EMBEDDED_2_0 || |
| 307 | env == SPV_ENV_OPENCL_EMBEDDED_2_1 || |
| 308 | env == SPV_ENV_OPENCL_EMBEDDED_2_2; |
| 309 | const std::string opencl_profile = opencl_embedded ? "Embedded" : "Full" ; |
| 310 | if (env == SPV_ENV_VULKAN_1_0) { |
| 311 | if (!IsSupportGuaranteedVulkan_1_0(capability) && |
| 312 | !IsSupportOptionalVulkan_1_0(capability) && |
| 313 | !IsEnabledByExtension(_, capability)) { |
| 314 | return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) |
| 315 | << "Capability " << capability_str() |
| 316 | << " is not allowed by Vulkan 1.0 specification" |
| 317 | << " (or requires extension)" ; |
| 318 | } |
| 319 | } else if (env == SPV_ENV_VULKAN_1_1) { |
| 320 | if (!IsSupportGuaranteedVulkan_1_1(capability) && |
| 321 | !IsSupportOptionalVulkan_1_1(capability) && |
| 322 | !IsEnabledByExtension(_, capability)) { |
| 323 | return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) |
| 324 | << "Capability " << capability_str() |
| 325 | << " is not allowed by Vulkan 1.1 specification" |
| 326 | << " (or requires extension)" ; |
| 327 | } |
| 328 | } else if (env == SPV_ENV_VULKAN_1_2) { |
| 329 | if (!IsSupportGuaranteedVulkan_1_2(capability) && |
| 330 | !IsSupportOptionalVulkan_1_2(capability) && |
| 331 | !IsEnabledByExtension(_, capability)) { |
| 332 | return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) |
| 333 | << "Capability " << capability_str() |
| 334 | << " is not allowed by Vulkan 1.2 specification" |
| 335 | << " (or requires extension)" ; |
| 336 | } |
| 337 | } else if (env == SPV_ENV_OPENCL_1_2 || env == SPV_ENV_OPENCL_EMBEDDED_1_2) { |
| 338 | if (!IsSupportGuaranteedOpenCL_1_2(capability, opencl_embedded) && |
| 339 | !IsSupportOptionalOpenCL_1_2(capability) && |
| 340 | !IsEnabledByExtension(_, capability) && |
| 341 | !IsEnabledByCapabilityOpenCL_1_2(_, capability)) { |
| 342 | return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) |
| 343 | << "Capability " << capability_str() |
| 344 | << " is not allowed by OpenCL 1.2 " << opencl_profile |
| 345 | << " Profile specification" |
| 346 | << " (or requires extension or capability)" ; |
| 347 | } |
| 348 | } else if (env == SPV_ENV_OPENCL_2_0 || env == SPV_ENV_OPENCL_EMBEDDED_2_0 || |
| 349 | env == SPV_ENV_OPENCL_2_1 || env == SPV_ENV_OPENCL_EMBEDDED_2_1) { |
| 350 | if (!IsSupportGuaranteedOpenCL_2_0(capability, opencl_embedded) && |
| 351 | !IsSupportOptionalOpenCL_1_2(capability) && |
| 352 | !IsEnabledByExtension(_, capability) && |
| 353 | !IsEnabledByCapabilityOpenCL_2_0(_, capability)) { |
| 354 | return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) |
| 355 | << "Capability " << capability_str() |
| 356 | << " is not allowed by OpenCL 2.0/2.1 " << opencl_profile |
| 357 | << " Profile specification" |
| 358 | << " (or requires extension or capability)" ; |
| 359 | } |
| 360 | } else if (env == SPV_ENV_OPENCL_2_2 || env == SPV_ENV_OPENCL_EMBEDDED_2_2) { |
| 361 | if (!IsSupportGuaranteedOpenCL_2_2(capability, opencl_embedded) && |
| 362 | !IsSupportOptionalOpenCL_1_2(capability) && |
| 363 | !IsEnabledByExtension(_, capability) && |
| 364 | !IsEnabledByCapabilityOpenCL_2_0(_, capability)) { |
| 365 | return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) |
| 366 | << "Capability " << capability_str() |
| 367 | << " is not allowed by OpenCL 2.2 " << opencl_profile |
| 368 | << " Profile specification" |
| 369 | << " (or requires extension or capability)" ; |
| 370 | } |
| 371 | } else if (env == SPV_ENV_WEBGPU_0) { |
| 372 | if (!IsSupportGuaranteedWebGPU(capability) && |
| 373 | !IsEnabledByExtension(_, capability)) { |
| 374 | return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst) |
| 375 | << "Capability " << capability_str() |
| 376 | << " is not allowed by WebGPU specification" |
| 377 | << " (or requires extension)" ; |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | return SPV_SUCCESS; |
| 382 | } |
| 383 | |
| 384 | } // namespace val |
| 385 | } // namespace spvtools |
| 386 | |