mirror of
https://github.com/bkaradzic/bgfx.git
synced 2026-02-17 20:52:36 +01:00
1143 lines
49 KiB
C++
1143 lines
49 KiB
C++
// Copyright (c) 2018 Google LLC.
|
|
// Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights
|
|
// reserved.
|
|
//
|
|
// 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 <algorithm>
|
|
|
|
#include "source/opcode.h"
|
|
#include "source/spirv_target_env.h"
|
|
#include "source/table2.h"
|
|
#include "source/val/instruction.h"
|
|
#include "source/val/validate.h"
|
|
#include "source/val/validation_state.h"
|
|
|
|
namespace spvtools {
|
|
namespace val {
|
|
namespace {
|
|
|
|
// TODO - Make a common util if someone else needs it too outside this file
|
|
const char* ExecutionModelToString(spv::ExecutionModel value) {
|
|
switch (value) {
|
|
case spv::ExecutionModel::Vertex:
|
|
return "Vertex";
|
|
case spv::ExecutionModel::TessellationControl:
|
|
return "TessellationControl";
|
|
case spv::ExecutionModel::TessellationEvaluation:
|
|
return "TessellationEvaluation";
|
|
case spv::ExecutionModel::Geometry:
|
|
return "Geometry";
|
|
case spv::ExecutionModel::Fragment:
|
|
return "Fragment";
|
|
case spv::ExecutionModel::GLCompute:
|
|
return "GLCompute";
|
|
case spv::ExecutionModel::Kernel:
|
|
return "Kernel";
|
|
case spv::ExecutionModel::TaskNV:
|
|
return "TaskNV";
|
|
case spv::ExecutionModel::MeshNV:
|
|
return "MeshNV";
|
|
case spv::ExecutionModel::RayGenerationKHR:
|
|
return "RayGenerationKHR";
|
|
case spv::ExecutionModel::IntersectionKHR:
|
|
return "IntersectionKHR";
|
|
case spv::ExecutionModel::AnyHitKHR:
|
|
return "AnyHitKHR";
|
|
case spv::ExecutionModel::ClosestHitKHR:
|
|
return "ClosestHitKHR";
|
|
case spv::ExecutionModel::MissKHR:
|
|
return "MissKHR";
|
|
case spv::ExecutionModel::CallableKHR:
|
|
return "CallableKHR";
|
|
case spv::ExecutionModel::TaskEXT:
|
|
return "TaskEXT";
|
|
case spv::ExecutionModel::MeshEXT:
|
|
return "MeshEXT";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
spv_result_t ValidateEntryPoint(ValidationState_t& _, const Instruction* inst) {
|
|
const auto entry_point_id = inst->GetOperandAs<uint32_t>(1);
|
|
auto entry_point = _.FindDef(entry_point_id);
|
|
if (!entry_point || spv::Op::OpFunction != entry_point->opcode()) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "OpEntryPoint Entry Point <id> " << _.getIdName(entry_point_id)
|
|
<< " is not a function.";
|
|
}
|
|
|
|
// Only check the shader execution models
|
|
const spv::ExecutionModel execution_model =
|
|
inst->GetOperandAs<spv::ExecutionModel>(0);
|
|
if (execution_model != spv::ExecutionModel::Kernel) {
|
|
const auto entry_point_type_id = entry_point->GetOperandAs<uint32_t>(3);
|
|
const auto entry_point_type = _.FindDef(entry_point_type_id);
|
|
if (!entry_point_type || 3 != entry_point_type->words().size()) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> "
|
|
<< _.getIdName(entry_point_id)
|
|
<< "s function parameter count is not zero.";
|
|
}
|
|
}
|
|
|
|
auto return_type = _.FindDef(entry_point->type_id());
|
|
if (!return_type || spv::Op::OpTypeVoid != return_type->opcode()) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< _.VkErrorID(4633) << "OpEntryPoint Entry Point <id> "
|
|
<< _.getIdName(entry_point_id)
|
|
<< "s function return type is not void.";
|
|
}
|
|
|
|
const auto* execution_modes = _.GetExecutionModes(entry_point_id);
|
|
auto has_mode = [&execution_modes](spv::ExecutionMode mode) {
|
|
return execution_modes && execution_modes->count(mode);
|
|
};
|
|
|
|
if (_.HasCapability(spv::Capability::Shader)) {
|
|
switch (execution_model) {
|
|
case spv::ExecutionModel::Fragment:
|
|
if (has_mode(spv::ExecutionMode::OriginUpperLeft) &&
|
|
has_mode(spv::ExecutionMode::OriginLowerLeft)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Fragment execution model entry points can only specify "
|
|
"one of OriginUpperLeft or OriginLowerLeft execution "
|
|
"modes.";
|
|
}
|
|
if (!has_mode(spv::ExecutionMode::OriginUpperLeft) &&
|
|
!has_mode(spv::ExecutionMode::OriginLowerLeft)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Fragment execution model entry points require either an "
|
|
"OriginUpperLeft or OriginLowerLeft execution mode.";
|
|
}
|
|
if (execution_modes &&
|
|
1 < std::count_if(execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::DepthGreater:
|
|
case spv::ExecutionMode::DepthLess:
|
|
case spv::ExecutionMode::DepthUnchanged:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Fragment execution model entry points can specify at most "
|
|
"one of DepthGreater, DepthLess or DepthUnchanged "
|
|
"execution modes.";
|
|
}
|
|
if (execution_modes &&
|
|
1 < std::count_if(
|
|
execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::PixelInterlockOrderedEXT:
|
|
case spv::ExecutionMode::PixelInterlockUnorderedEXT:
|
|
case spv::ExecutionMode::SampleInterlockOrderedEXT:
|
|
case spv::ExecutionMode::SampleInterlockUnorderedEXT:
|
|
case spv::ExecutionMode::ShadingRateInterlockOrderedEXT:
|
|
case spv::ExecutionMode::
|
|
ShadingRateInterlockUnorderedEXT:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Fragment execution model entry points can specify at most "
|
|
"one fragment shader interlock execution mode.";
|
|
}
|
|
if (execution_modes &&
|
|
1 < std::count_if(
|
|
execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::StencilRefUnchangedFrontAMD:
|
|
case spv::ExecutionMode::StencilRefLessFrontAMD:
|
|
case spv::ExecutionMode::StencilRefGreaterFrontAMD:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Fragment execution model entry points can specify at most "
|
|
"one of StencilRefUnchangedFrontAMD, "
|
|
"StencilRefLessFrontAMD or StencilRefGreaterFrontAMD "
|
|
"execution modes.";
|
|
}
|
|
if (execution_modes &&
|
|
1 < std::count_if(
|
|
execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::StencilRefUnchangedBackAMD:
|
|
case spv::ExecutionMode::StencilRefLessBackAMD:
|
|
case spv::ExecutionMode::StencilRefGreaterBackAMD:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Fragment execution model entry points can specify at most "
|
|
"one of StencilRefUnchangedBackAMD, "
|
|
"StencilRefLessBackAMD or StencilRefGreaterBackAMD "
|
|
"execution modes.";
|
|
}
|
|
break;
|
|
case spv::ExecutionModel::TessellationControl:
|
|
case spv::ExecutionModel::TessellationEvaluation:
|
|
if (execution_modes &&
|
|
1 < std::count_if(
|
|
execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::SpacingEqual:
|
|
case spv::ExecutionMode::SpacingFractionalEven:
|
|
case spv::ExecutionMode::SpacingFractionalOdd:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Tessellation execution model entry points can specify at "
|
|
"most one of SpacingEqual, SpacingFractionalOdd or "
|
|
"SpacingFractionalEven execution modes.";
|
|
}
|
|
if (execution_modes &&
|
|
1 < std::count_if(execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::Triangles:
|
|
case spv::ExecutionMode::Quads:
|
|
case spv::ExecutionMode::Isolines:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Tessellation execution model entry points can specify at "
|
|
"most one of Triangles, Quads or Isolines execution modes.";
|
|
}
|
|
if (execution_modes &&
|
|
1 < std::count_if(execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::VertexOrderCw:
|
|
case spv::ExecutionMode::VertexOrderCcw:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Tessellation execution model entry points can specify at "
|
|
"most one of VertexOrderCw or VertexOrderCcw execution "
|
|
"modes.";
|
|
}
|
|
break;
|
|
case spv::ExecutionModel::Geometry:
|
|
if (!execution_modes ||
|
|
1 != std::count_if(
|
|
execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::InputPoints:
|
|
case spv::ExecutionMode::InputLines:
|
|
case spv::ExecutionMode::InputLinesAdjacency:
|
|
case spv::ExecutionMode::Triangles:
|
|
case spv::ExecutionMode::InputTrianglesAdjacency:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Geometry execution model entry points must specify "
|
|
"exactly one of InputPoints, InputLines, "
|
|
"InputLinesAdjacency, Triangles or InputTrianglesAdjacency "
|
|
"execution modes.";
|
|
}
|
|
if (!execution_modes ||
|
|
1 != std::count_if(execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::OutputPoints:
|
|
case spv::ExecutionMode::OutputLineStrip:
|
|
case spv::ExecutionMode::OutputTriangleStrip:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Geometry execution model entry points must specify "
|
|
"exactly one of OutputPoints, OutputLineStrip or "
|
|
"OutputTriangleStrip execution modes.";
|
|
}
|
|
break;
|
|
case spv::ExecutionModel::MeshEXT:
|
|
if (!execution_modes ||
|
|
1 != std::count_if(execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::OutputPoints:
|
|
case spv::ExecutionMode::OutputLinesEXT:
|
|
case spv::ExecutionMode::OutputTrianglesEXT:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "MeshEXT execution model entry points must specify exactly "
|
|
"one of OutputPoints, OutputLinesEXT, or "
|
|
"OutputTrianglesEXT Execution Modes.";
|
|
} else if (2 != std::count_if(
|
|
execution_modes->begin(), execution_modes->end(),
|
|
[](const spv::ExecutionMode& mode) {
|
|
switch (mode) {
|
|
case spv::ExecutionMode::OutputPrimitivesEXT:
|
|
case spv::ExecutionMode::OutputVertices:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "MeshEXT execution model entry points must specify both "
|
|
"OutputPrimitivesEXT and OutputVertices Execution Modes.";
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool has_workgroup_size = false;
|
|
bool has_local_size_id = false;
|
|
for (auto& i : _.ordered_instructions()) {
|
|
if (i.opcode() == spv::Op::OpFunction) break;
|
|
if (i.opcode() == spv::Op::OpDecorate && i.operands().size() > 2) {
|
|
if (i.GetOperandAs<spv::Decoration>(1) == spv::Decoration::BuiltIn &&
|
|
i.GetOperandAs<spv::BuiltIn>(2) == spv::BuiltIn::WorkgroupSize) {
|
|
has_workgroup_size = true;
|
|
}
|
|
}
|
|
if (i.opcode() == spv::Op::OpExecutionModeId) {
|
|
if (i.GetOperandAs<spv::ExecutionMode>(1) ==
|
|
spv::ExecutionMode::LocalSizeId) {
|
|
has_local_size_id = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (spvIsVulkanEnv(_.context()->target_env)) {
|
|
// SPV_QCOM_tile_shading checks
|
|
if (execution_model == spv::ExecutionModel::GLCompute) {
|
|
if (_.HasCapability(spv::Capability::TileShadingQCOM)) {
|
|
if (has_mode(spv::ExecutionMode::TileShadingRateQCOM) &&
|
|
(has_mode(spv::ExecutionMode::LocalSize) ||
|
|
has_mode(spv::ExecutionMode::LocalSizeId))) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "If the TileShadingRateQCOM execution mode is used, "
|
|
<< "LocalSize and LocalSizeId must not be specified.";
|
|
}
|
|
if (has_mode(spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "The NonCoherentTileAttachmentQCOM execution mode must "
|
|
"not be used in any stage other than fragment.";
|
|
}
|
|
} else {
|
|
if (has_mode(spv::ExecutionMode::TileShadingRateQCOM)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "If the TileShadingRateQCOM execution mode is used, the "
|
|
"TileShadingQCOM capability must be enabled.";
|
|
}
|
|
}
|
|
} else {
|
|
if (has_mode(spv::ExecutionMode::TileShadingRateQCOM)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "The TileShadingRateQCOM execution mode must not be used "
|
|
"in any stage other than compute.";
|
|
}
|
|
if (execution_model != spv::ExecutionModel::Fragment) {
|
|
if (has_mode(spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "The NonCoherentTileAttachmentQCOM execution mode must "
|
|
"not be used in any stage other than fragment.";
|
|
}
|
|
if (_.HasCapability(spv::Capability::TileShadingQCOM)) {
|
|
return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
|
|
<< "The TileShadingQCOM capability must not be enabled in "
|
|
"any stage other than compute or fragment.";
|
|
}
|
|
} else {
|
|
if (has_mode(spv::ExecutionMode::NonCoherentTileAttachmentReadQCOM)) {
|
|
if (!_.HasCapability(spv::Capability::TileShadingQCOM)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "If the NonCoherentTileAttachmentReadQCOM execution "
|
|
"mode is used, the TileShadingQCOM capability must be "
|
|
"enabled.";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (execution_model) {
|
|
case spv::ExecutionModel::GLCompute:
|
|
case spv::ExecutionModel::MeshEXT:
|
|
case spv::ExecutionModel::MeshNV:
|
|
case spv::ExecutionModel::TaskEXT:
|
|
case spv::ExecutionModel::TaskNV:
|
|
if (!has_mode(spv::ExecutionMode::LocalSize) && !has_workgroup_size &&
|
|
!has_local_size_id &&
|
|
!has_mode(spv::ExecutionMode::TileShadingRateQCOM)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< _.VkErrorID(10685) << "In the Vulkan environment, "
|
|
<< ExecutionModelToString(execution_model)
|
|
<< " execution model "
|
|
"entry points require either the "
|
|
<< (_.HasCapability(spv::Capability::TileShadingQCOM)
|
|
? "TileShadingRateQCOM, "
|
|
: "")
|
|
<< "LocalSize or LocalSizeId execution mode or an object "
|
|
"decorated with WorkgroupSize must be specified.";
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// WorkgroupSize decoration takes precedence over any LocalSize or LocalSizeId
|
|
// execution mode, so the values can be ignored
|
|
if (_.EntryPointHasLocalSizeOrId(entry_point_id) && !has_workgroup_size) {
|
|
const Instruction* local_size_inst =
|
|
_.EntryPointLocalSizeOrId(entry_point_id);
|
|
if (local_size_inst) {
|
|
const auto mode = local_size_inst->GetOperandAs<spv::ExecutionMode>(1);
|
|
const uint32_t operand_x = local_size_inst->GetOperandAs<uint32_t>(2);
|
|
const uint32_t operand_y = local_size_inst->GetOperandAs<uint32_t>(3);
|
|
const uint32_t operand_z = local_size_inst->GetOperandAs<uint32_t>(4);
|
|
if (mode == spv::ExecutionMode::LocalSize) {
|
|
const uint64_t product_size = operand_x * operand_y * operand_z;
|
|
if (product_size == 0) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
|
|
<< "Local Size execution mode must not have a product of zero "
|
|
"(X "
|
|
"= "
|
|
<< operand_x << ", Y = " << operand_y << ", Z = " << operand_z
|
|
<< ").";
|
|
}
|
|
if (has_mode(spv::ExecutionMode::DerivativeGroupQuadsKHR)) {
|
|
if (operand_x % 2 != 0 || operand_y % 2 != 0) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
|
|
<< _.VkErrorID(10151)
|
|
<< "Local Size execution mode dimensions is "
|
|
"(X = "
|
|
<< operand_x << ", Y = " << operand_y
|
|
<< ") but Entry Point id " << entry_point_id
|
|
<< " also has an DerivativeGroupQuadsKHR execution mode, so "
|
|
"both dimensions must be a multiple of 2";
|
|
}
|
|
}
|
|
if (has_mode(spv::ExecutionMode::DerivativeGroupLinearKHR)) {
|
|
if (product_size % 4 != 0) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
|
|
<< _.VkErrorID(10152)
|
|
<< "Local Size execution mode dimensions is (X = "
|
|
<< operand_x << ", Y = " << operand_y
|
|
<< ", Z = " << operand_z << ") but Entry Point id "
|
|
<< entry_point_id
|
|
<< " also has an DerivativeGroupLinearKHR execution mode, "
|
|
"so "
|
|
"the product ("
|
|
<< product_size << ") must be a multiple of 4";
|
|
}
|
|
}
|
|
} else if (mode == spv::ExecutionMode::LocalSizeId) {
|
|
// can only validate product if static and not spec constant
|
|
// (This is done for us in EvalConstantValUint64)
|
|
uint64_t x_size, y_size, z_size;
|
|
bool static_x = _.EvalConstantValUint64(operand_x, &x_size);
|
|
bool static_y = _.EvalConstantValUint64(operand_y, &y_size);
|
|
bool static_z = _.EvalConstantValUint64(operand_z, &z_size);
|
|
if (static_x && static_y && static_z) {
|
|
const uint64_t product_size = x_size * y_size * z_size;
|
|
if (product_size == 0) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
|
|
<< "LocalSizeId execution mode must not have a product of "
|
|
"zero "
|
|
"(X = "
|
|
<< x_size << ", Y = " << y_size << ", Z = " << z_size
|
|
<< ").";
|
|
}
|
|
if (has_mode(spv::ExecutionMode::DerivativeGroupQuadsKHR)) {
|
|
if (x_size % 2 != 0 || y_size % 2 != 0) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
|
|
<< _.VkErrorID(10151)
|
|
<< "LocalSizeId execution mode dimensions is "
|
|
"(X = "
|
|
<< x_size << ", Y = " << y_size << ") but Entry Point id "
|
|
<< entry_point_id
|
|
<< " also has an DerivativeGroupQuadsKHR execution mode, "
|
|
"so "
|
|
"both dimensions must be a multiple of 2";
|
|
}
|
|
}
|
|
if (has_mode(spv::ExecutionMode::DerivativeGroupLinearKHR)) {
|
|
if (product_size % 4 != 0) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, local_size_inst)
|
|
<< _.VkErrorID(10152)
|
|
<< "LocalSizeId execution mode dimensions is (X = "
|
|
<< x_size << ", Y = " << y_size << ", Z = " << z_size
|
|
<< ") but Entry Point id " << entry_point_id
|
|
<< " also has an DerivativeGroupLinearKHR execution mode, "
|
|
"so "
|
|
"the product ("
|
|
<< product_size << ") must be a multiple of 4";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
spv_result_t ValidateExecutionMode(ValidationState_t& _,
|
|
const Instruction* inst) {
|
|
const auto entry_point_id = inst->GetOperandAs<uint32_t>(0);
|
|
const auto found = std::find(_.entry_points().cbegin(),
|
|
_.entry_points().cend(), entry_point_id);
|
|
if (found == _.entry_points().cend()) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "OpExecutionMode Entry Point <id> " << _.getIdName(entry_point_id)
|
|
<< " is not the Entry Point "
|
|
"operand of an OpEntryPoint.";
|
|
}
|
|
|
|
const auto mode = inst->GetOperandAs<spv::ExecutionMode>(1);
|
|
if (inst->opcode() == spv::Op::OpExecutionModeId) {
|
|
bool valid_mode = false;
|
|
switch (mode) {
|
|
case spv::ExecutionMode::SubgroupsPerWorkgroupId:
|
|
case spv::ExecutionMode::LocalSizeHintId:
|
|
case spv::ExecutionMode::LocalSizeId:
|
|
case spv::ExecutionMode::FPFastMathDefault:
|
|
case spv::ExecutionMode::MaximumRegistersIdINTEL:
|
|
case spv::ExecutionMode::IsApiEntryAMDX:
|
|
case spv::ExecutionMode::MaxNodeRecursionAMDX:
|
|
case spv::ExecutionMode::MaxNumWorkgroupsAMDX:
|
|
case spv::ExecutionMode::ShaderIndexAMDX:
|
|
case spv::ExecutionMode::SharesInputWithAMDX:
|
|
case spv::ExecutionMode::StaticNumWorkgroupsAMDX:
|
|
valid_mode = true;
|
|
break;
|
|
default:
|
|
valid_mode = false;
|
|
break;
|
|
}
|
|
if (!valid_mode) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "OpExecutionModeId is only valid when the Mode operand is an "
|
|
"execution mode that takes Extra Operands that are id "
|
|
"operands.";
|
|
}
|
|
|
|
size_t operand_count = inst->operands().size();
|
|
for (size_t i = 2; i < operand_count; ++i) {
|
|
const auto operand_id = inst->GetOperandAs<uint32_t>(i);
|
|
const auto* operand_inst = _.FindDef(operand_id);
|
|
switch (mode) {
|
|
case spv::ExecutionMode::SubgroupsPerWorkgroupId:
|
|
case spv::ExecutionMode::LocalSizeHintId:
|
|
case spv::ExecutionMode::LocalSizeId:
|
|
case spv::ExecutionMode::IsApiEntryAMDX:
|
|
case spv::ExecutionMode::MaxNodeRecursionAMDX:
|
|
case spv::ExecutionMode::MaxNumWorkgroupsAMDX:
|
|
case spv::ExecutionMode::ShaderIndexAMDX:
|
|
case spv::ExecutionMode::SharesInputWithAMDX:
|
|
case spv::ExecutionMode::StaticNumWorkgroupsAMDX:
|
|
if (!spvOpcodeIsConstant(operand_inst->opcode())) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "For OpExecutionModeId all Extra Operand ids must be "
|
|
"constant instructions.";
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::FPFastMathDefault:
|
|
if (i == 2) {
|
|
if (!_.IsFloatScalarType(operand_id)) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "The Target Type operand must be a floating-point "
|
|
"scalar type";
|
|
}
|
|
} else {
|
|
bool is_int32 = false;
|
|
bool is_const = false;
|
|
uint32_t value = 0;
|
|
std::tie(is_int32, is_const, value) =
|
|
_.EvalInt32IfConst(operand_id);
|
|
if (is_int32 && is_const) {
|
|
// Valid values include up to 0x00040000 (AllowTransform).
|
|
uint32_t invalid_mask = 0xfff80000;
|
|
if ((invalid_mask & value) != 0) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "The Fast Math Default operand is an invalid bitmask "
|
|
"value";
|
|
}
|
|
if (value &
|
|
static_cast<uint32_t>(spv::FPFastMathModeMask::Fast)) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "The Fast Math Default operand must not include Fast";
|
|
}
|
|
const auto reassoc_contract =
|
|
spv::FPFastMathModeMask::AllowContract |
|
|
spv::FPFastMathModeMask::AllowReassoc;
|
|
if ((value & static_cast<uint32_t>(
|
|
spv::FPFastMathModeMask::AllowTransform)) != 0 &&
|
|
((value & static_cast<uint32_t>(reassoc_contract)) !=
|
|
static_cast<uint32_t>(reassoc_contract))) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "The Fast Math Default operand must include "
|
|
"AllowContract and AllowReassoc when AllowTransform "
|
|
"is specified";
|
|
}
|
|
} else {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "The Fast Math Default operand must be a "
|
|
"non-specialization constant";
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
} else if (mode == spv::ExecutionMode::SubgroupsPerWorkgroupId ||
|
|
mode == spv::ExecutionMode::LocalSizeHintId ||
|
|
mode == spv::ExecutionMode::LocalSizeId ||
|
|
mode == spv::ExecutionMode::FPFastMathDefault ||
|
|
mode == spv::ExecutionMode::IsApiEntryAMDX ||
|
|
mode == spv::ExecutionMode::MaxNodeRecursionAMDX ||
|
|
mode == spv::ExecutionMode::MaxNumWorkgroupsAMDX ||
|
|
mode == spv::ExecutionMode::ShaderIndexAMDX ||
|
|
mode == spv::ExecutionMode::SharesInputWithAMDX ||
|
|
mode == spv::ExecutionMode::StaticNumWorkgroupsAMDX) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "OpExecutionMode is only valid when the Mode operand is an "
|
|
"execution mode that takes no Extra Operands, or takes Extra "
|
|
"Operands that are not id operands.";
|
|
}
|
|
|
|
const bool is_vulkan_env = (spvIsVulkanEnv(_.context()->target_env));
|
|
const auto* models = _.GetExecutionModels(entry_point_id);
|
|
switch (mode) {
|
|
case spv::ExecutionMode::Invocations:
|
|
case spv::ExecutionMode::InputPoints:
|
|
case spv::ExecutionMode::InputLines:
|
|
case spv::ExecutionMode::InputLinesAdjacency:
|
|
case spv::ExecutionMode::InputTrianglesAdjacency:
|
|
case spv::ExecutionMode::OutputLineStrip:
|
|
case spv::ExecutionMode::OutputTriangleStrip:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const spv::ExecutionModel& model) {
|
|
return model == spv::ExecutionModel::Geometry;
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Geometry execution "
|
|
"model.";
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::OutputPoints:
|
|
if (!std::all_of(
|
|
models->begin(), models->end(),
|
|
[&_](const spv::ExecutionModel& model) {
|
|
switch (model) {
|
|
case spv::ExecutionModel::Geometry:
|
|
return true;
|
|
case spv::ExecutionModel::MeshNV:
|
|
return _.HasCapability(spv::Capability::MeshShadingNV);
|
|
case spv::ExecutionModel::MeshEXT:
|
|
return _.HasCapability(spv::Capability::MeshShadingEXT);
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
if (_.HasCapability(spv::Capability::MeshShadingNV) ||
|
|
_.HasCapability(spv::Capability::MeshShadingEXT)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Geometry "
|
|
"MeshNV or MeshEXT execution model.";
|
|
} else {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Geometry "
|
|
"execution "
|
|
"model.";
|
|
}
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::SpacingEqual:
|
|
case spv::ExecutionMode::SpacingFractionalEven:
|
|
case spv::ExecutionMode::SpacingFractionalOdd:
|
|
case spv::ExecutionMode::VertexOrderCw:
|
|
case spv::ExecutionMode::VertexOrderCcw:
|
|
case spv::ExecutionMode::PointMode:
|
|
case spv::ExecutionMode::Quads:
|
|
case spv::ExecutionMode::Isolines:
|
|
if (!std::all_of(
|
|
models->begin(), models->end(),
|
|
[](const spv::ExecutionModel& model) {
|
|
return (model == spv::ExecutionModel::TessellationControl) ||
|
|
(model == spv::ExecutionModel::TessellationEvaluation);
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a tessellation "
|
|
"execution model.";
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::Triangles:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const spv::ExecutionModel& model) {
|
|
switch (model) {
|
|
case spv::ExecutionModel::Geometry:
|
|
case spv::ExecutionModel::TessellationControl:
|
|
case spv::ExecutionModel::TessellationEvaluation:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a Geometry or "
|
|
"tessellation execution model.";
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::OutputVertices:
|
|
if (!std::all_of(
|
|
models->begin(), models->end(),
|
|
[&_](const spv::ExecutionModel& model) {
|
|
switch (model) {
|
|
case spv::ExecutionModel::Geometry:
|
|
case spv::ExecutionModel::TessellationControl:
|
|
case spv::ExecutionModel::TessellationEvaluation:
|
|
return true;
|
|
case spv::ExecutionModel::MeshNV:
|
|
return _.HasCapability(spv::Capability::MeshShadingNV);
|
|
case spv::ExecutionModel::MeshEXT:
|
|
return _.HasCapability(spv::Capability::MeshShadingEXT);
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
if (_.HasCapability(spv::Capability::MeshShadingNV) ||
|
|
_.HasCapability(spv::Capability::MeshShadingEXT)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a Geometry, "
|
|
"tessellation, MeshNV or MeshEXT execution model.";
|
|
} else {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a Geometry or "
|
|
"tessellation execution model.";
|
|
}
|
|
}
|
|
if (is_vulkan_env) {
|
|
if (_.HasCapability(spv::Capability::MeshShadingEXT) &&
|
|
inst->GetOperandAs<uint32_t>(2) == 0) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< _.VkErrorID(7330)
|
|
<< "In mesh shaders using the MeshEXT Execution Model the "
|
|
"OutputVertices Execution Mode must be greater than 0";
|
|
}
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::OutputLinesEXT:
|
|
case spv::ExecutionMode::OutputTrianglesEXT:
|
|
case spv::ExecutionMode::OutputPrimitivesEXT:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const spv::ExecutionModel& model) {
|
|
return (model == spv::ExecutionModel::MeshEXT ||
|
|
model == spv::ExecutionModel::MeshNV);
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the MeshEXT or MeshNV "
|
|
"execution "
|
|
"model.";
|
|
}
|
|
if (mode == spv::ExecutionMode::OutputPrimitivesEXT && is_vulkan_env) {
|
|
if (_.HasCapability(spv::Capability::MeshShadingEXT) &&
|
|
inst->GetOperandAs<uint32_t>(2) == 0) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< _.VkErrorID(7331)
|
|
<< "In mesh shaders using the MeshEXT Execution Model the "
|
|
"OutputPrimitivesEXT Execution Mode must be greater than 0";
|
|
}
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::QuadDerivativesKHR:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const spv::ExecutionModel& model) {
|
|
return (model == spv::ExecutionModel::Fragment ||
|
|
model == spv::ExecutionModel::GLCompute);
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Fragment or "
|
|
"GLCompute execution model.";
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::PixelCenterInteger:
|
|
case spv::ExecutionMode::OriginUpperLeft:
|
|
case spv::ExecutionMode::OriginLowerLeft:
|
|
case spv::ExecutionMode::EarlyFragmentTests:
|
|
case spv::ExecutionMode::DepthReplacing:
|
|
case spv::ExecutionMode::DepthGreater:
|
|
case spv::ExecutionMode::DepthLess:
|
|
case spv::ExecutionMode::DepthUnchanged:
|
|
case spv::ExecutionMode::NonCoherentColorAttachmentReadEXT:
|
|
case spv::ExecutionMode::NonCoherentDepthAttachmentReadEXT:
|
|
case spv::ExecutionMode::NonCoherentStencilAttachmentReadEXT:
|
|
case spv::ExecutionMode::PixelInterlockOrderedEXT:
|
|
case spv::ExecutionMode::PixelInterlockUnorderedEXT:
|
|
case spv::ExecutionMode::SampleInterlockOrderedEXT:
|
|
case spv::ExecutionMode::SampleInterlockUnorderedEXT:
|
|
case spv::ExecutionMode::ShadingRateInterlockOrderedEXT:
|
|
case spv::ExecutionMode::ShadingRateInterlockUnorderedEXT:
|
|
case spv::ExecutionMode::EarlyAndLateFragmentTestsAMD:
|
|
case spv::ExecutionMode::StencilRefUnchangedFrontAMD:
|
|
case spv::ExecutionMode::StencilRefGreaterFrontAMD:
|
|
case spv::ExecutionMode::StencilRefLessFrontAMD:
|
|
case spv::ExecutionMode::StencilRefUnchangedBackAMD:
|
|
case spv::ExecutionMode::StencilRefGreaterBackAMD:
|
|
case spv::ExecutionMode::StencilRefLessBackAMD:
|
|
case spv::ExecutionMode::RequireFullQuadsKHR:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const spv::ExecutionModel& model) {
|
|
return model == spv::ExecutionModel::Fragment;
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Fragment execution "
|
|
"model.";
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::LocalSizeHint:
|
|
case spv::ExecutionMode::VecTypeHint:
|
|
case spv::ExecutionMode::ContractionOff:
|
|
case spv::ExecutionMode::LocalSizeHintId:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const spv::ExecutionModel& model) {
|
|
return model == spv::ExecutionModel::Kernel;
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Kernel execution "
|
|
"model.";
|
|
}
|
|
break;
|
|
case spv::ExecutionMode::LocalSize:
|
|
case spv::ExecutionMode::LocalSizeId:
|
|
if (mode == spv::ExecutionMode::LocalSizeId &&
|
|
!_.IsLocalSizeIdAllowed()) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "LocalSizeId mode is not allowed by the current environment."
|
|
<< (is_vulkan_env
|
|
? _.MissingFeature("maintenance4 feature",
|
|
"--allow-localsizeid", false)
|
|
: "");
|
|
}
|
|
|
|
if (!std::all_of(
|
|
models->begin(), models->end(),
|
|
[&_](const spv::ExecutionModel& model) {
|
|
switch (model) {
|
|
case spv::ExecutionModel::Kernel:
|
|
case spv::ExecutionModel::GLCompute:
|
|
return true;
|
|
case spv::ExecutionModel::TaskNV:
|
|
case spv::ExecutionModel::MeshNV:
|
|
return _.HasCapability(spv::Capability::MeshShadingNV);
|
|
case spv::ExecutionModel::TaskEXT:
|
|
case spv::ExecutionModel::MeshEXT:
|
|
return _.HasCapability(spv::Capability::MeshShadingEXT);
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
if (_.HasCapability(spv::Capability::MeshShadingNV) ||
|
|
_.HasCapability(spv::Capability::MeshShadingEXT)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a Kernel, GLCompute, "
|
|
"MeshNV, MeshEXT, TaskNV or TaskEXT execution model.";
|
|
} else {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a Kernel or "
|
|
"GLCompute "
|
|
"execution model.";
|
|
}
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (mode == spv::ExecutionMode::FPFastMathDefault) {
|
|
const auto* modes = _.GetExecutionModes(entry_point_id);
|
|
if (modes && modes->count(spv::ExecutionMode::ContractionOff)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "FPFastMathDefault and ContractionOff execution modes cannot "
|
|
"be applied to the same entry point";
|
|
}
|
|
if (modes && modes->count(spv::ExecutionMode::SignedZeroInfNanPreserve)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "FPFastMathDefault and SignedZeroInfNanPreserve execution "
|
|
"modes cannot be applied to the same entry point";
|
|
}
|
|
}
|
|
|
|
if (is_vulkan_env) {
|
|
if (mode == spv::ExecutionMode::OriginLowerLeft) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< _.VkErrorID(4653)
|
|
<< "In the Vulkan environment, the OriginLowerLeft execution mode "
|
|
"must not be used.";
|
|
}
|
|
if (mode == spv::ExecutionMode::PixelCenterInteger) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< _.VkErrorID(4654)
|
|
<< "In the Vulkan environment, the PixelCenterInteger execution "
|
|
"mode must not be used.";
|
|
}
|
|
if (mode == spv::ExecutionMode::TileShadingRateQCOM) {
|
|
const auto rateX = inst->GetOperandAs<int>(2);
|
|
const auto rateY = inst->GetOperandAs<int>(3);
|
|
if ((rateX & (rateX - 1)) != 0 || (rateY & (rateY - 1)) != 0)
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "The TileShadingRateQCOM execution mode's x and y values "
|
|
"must be powers of 2.";
|
|
}
|
|
}
|
|
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
spv_result_t ValidateMemoryModel(ValidationState_t& _,
|
|
const Instruction* inst) {
|
|
// Already produced an error if multiple memory model instructions are
|
|
// present.
|
|
if (_.memory_model() != spv::MemoryModel::VulkanKHR &&
|
|
_.HasCapability(spv::Capability::VulkanMemoryModelKHR)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "VulkanMemoryModelKHR capability must only be specified if "
|
|
"the VulkanKHR memory model is used.";
|
|
}
|
|
|
|
if (spvIsOpenCLEnv(_.context()->target_env)) {
|
|
if ((_.addressing_model() != spv::AddressingModel::Physical32) &&
|
|
(_.addressing_model() != spv::AddressingModel::Physical64)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Addressing model must be Physical32 or Physical64 "
|
|
<< "in the OpenCL environment.";
|
|
}
|
|
if (_.memory_model() != spv::MemoryModel::OpenCL) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Memory model must be OpenCL in the OpenCL environment.";
|
|
}
|
|
}
|
|
|
|
if (spvIsVulkanEnv(_.context()->target_env)) {
|
|
if ((_.addressing_model() != spv::AddressingModel::Logical) &&
|
|
(_.addressing_model() !=
|
|
spv::AddressingModel::PhysicalStorageBuffer64)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< _.VkErrorID(4635)
|
|
<< "Addressing model must be Logical or PhysicalStorageBuffer64 "
|
|
<< "in the Vulkan environment.";
|
|
}
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
bool PerEntryExecutionMode(spv::ExecutionMode mode) {
|
|
switch (mode) {
|
|
// These execution modes can be specified multiple times per entry point.
|
|
case spv::ExecutionMode::DenormPreserve:
|
|
case spv::ExecutionMode::DenormFlushToZero:
|
|
case spv::ExecutionMode::SignedZeroInfNanPreserve:
|
|
case spv::ExecutionMode::RoundingModeRTE:
|
|
case spv::ExecutionMode::RoundingModeRTZ:
|
|
case spv::ExecutionMode::FPFastMathDefault:
|
|
case spv::ExecutionMode::RoundingModeRTPINTEL:
|
|
case spv::ExecutionMode::RoundingModeRTNINTEL:
|
|
case spv::ExecutionMode::FloatingPointModeALTINTEL:
|
|
case spv::ExecutionMode::FloatingPointModeIEEEINTEL:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
spv_result_t ValidateCapability(ValidationState_t& _, const Instruction* inst) {
|
|
auto cap = inst->GetOperandAs<spv::Capability>(0);
|
|
if (cap == spv::Capability::CooperativeMatrixKHR) {
|
|
if (_.HasCapability(spv::Capability::Shader) &&
|
|
!_.HasCapability(spv::Capability::VulkanMemoryModel)) {
|
|
return _.diag(SPV_ERROR_INVALID_CAPABILITY, inst)
|
|
<< "If the Shader and CooperativeMatrixKHR capabilities are "
|
|
"declared, the VulkanMemoryModel capability must also be "
|
|
"declared";
|
|
}
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
spv_result_t ValidateFloatControls2(ValidationState_t& _) {
|
|
std::unordered_set<uint32_t> fp_fast_math_default_entry_points;
|
|
for (auto entry_point : _.entry_points()) {
|
|
const auto* exec_modes = _.GetExecutionModes(entry_point);
|
|
if (exec_modes &&
|
|
exec_modes->count(spv::ExecutionMode::FPFastMathDefault)) {
|
|
fp_fast_math_default_entry_points.insert(entry_point);
|
|
}
|
|
}
|
|
|
|
std::vector<std::pair<const Instruction*, spv::Decoration>> worklist;
|
|
for (const auto& inst : _.ordered_instructions()) {
|
|
if (inst.opcode() != spv::Op::OpDecorate) {
|
|
continue;
|
|
}
|
|
|
|
const auto decoration = inst.GetOperandAs<spv::Decoration>(1);
|
|
const auto target_id = inst.GetOperandAs<uint32_t>(0);
|
|
const auto target = _.FindDef(target_id);
|
|
if (decoration == spv::Decoration::NoContraction) {
|
|
worklist.push_back(std::make_pair(target, decoration));
|
|
} else if (decoration == spv::Decoration::FPFastMathMode) {
|
|
auto mask = inst.GetOperandAs<spv::FPFastMathModeMask>(2);
|
|
if ((mask & spv::FPFastMathModeMask::Fast) !=
|
|
spv::FPFastMathModeMask::MaskNone) {
|
|
worklist.push_back(std::make_pair(target, decoration));
|
|
}
|
|
}
|
|
}
|
|
|
|
std::unordered_set<const Instruction*> visited;
|
|
while (!worklist.empty()) {
|
|
const auto inst = worklist.back().first;
|
|
const auto decoration = worklist.back().second;
|
|
worklist.pop_back();
|
|
|
|
if (!visited.insert(inst).second) {
|
|
continue;
|
|
}
|
|
|
|
const auto function = inst->function();
|
|
if (function) {
|
|
const auto& entry_points = _.FunctionEntryPoints(function->id());
|
|
for (auto entry_point : entry_points) {
|
|
if (fp_fast_math_default_entry_points.count(entry_point)) {
|
|
const std::string dec = decoration == spv::Decoration::NoContraction
|
|
? "NoContraction"
|
|
: "FPFastMathMode Fast";
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< dec
|
|
<< " cannot be used by an entry point with the "
|
|
"FPFastMathDefault execution mode";
|
|
}
|
|
}
|
|
} else {
|
|
for (const auto& pair : inst->uses()) {
|
|
worklist.push_back(std::make_pair(pair.first, decoration));
|
|
}
|
|
}
|
|
}
|
|
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) {
|
|
switch (inst->opcode()) {
|
|
case spv::Op::OpEntryPoint:
|
|
if (auto error = ValidateEntryPoint(_, inst)) return error;
|
|
break;
|
|
case spv::Op::OpExecutionMode:
|
|
case spv::Op::OpExecutionModeId:
|
|
if (auto error = ValidateExecutionMode(_, inst)) return error;
|
|
break;
|
|
case spv::Op::OpMemoryModel:
|
|
if (auto error = ValidateMemoryModel(_, inst)) return error;
|
|
break;
|
|
case spv::Op::OpCapability:
|
|
if (auto error = ValidateCapability(_, inst)) return error;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
spv_result_t ValidateDuplicateExecutionModes(ValidationState_t& _) {
|
|
using PerEntryKey = std::tuple<spv::ExecutionMode, uint32_t>;
|
|
using PerOperandKey = std::tuple<spv::ExecutionMode, uint32_t, uint32_t>;
|
|
std::set<PerEntryKey> seen_per_entry;
|
|
std::set<PerOperandKey> seen_per_operand;
|
|
|
|
const auto lookupMode = [](spv::ExecutionMode mode) -> std::string {
|
|
const spvtools::OperandDesc* desc = nullptr;
|
|
if (spvtools::LookupOperand(SPV_OPERAND_TYPE_EXECUTION_MODE,
|
|
static_cast<uint32_t>(mode),
|
|
&desc) == SPV_SUCCESS) {
|
|
return std::string(desc->name().data());
|
|
}
|
|
return "Unknown";
|
|
};
|
|
|
|
for (const auto& inst : _.ordered_instructions()) {
|
|
if (inst.opcode() != spv::Op::OpExecutionMode &&
|
|
inst.opcode() != spv::Op::OpExecutionModeId) {
|
|
continue;
|
|
}
|
|
|
|
const auto entry = inst.GetOperandAs<uint32_t>(0);
|
|
const auto mode = inst.GetOperandAs<spv::ExecutionMode>(1);
|
|
if (PerEntryExecutionMode(mode)) {
|
|
if (!seen_per_entry.insert(std::make_tuple(mode, entry)).second) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, &inst)
|
|
<< lookupMode(mode)
|
|
<< " execution mode must not be specified multiple times per "
|
|
"entry point";
|
|
}
|
|
} else {
|
|
// Execution modes allowed multiple times all take a single operand.
|
|
const auto operand = inst.GetOperandAs<uint32_t>(2);
|
|
if (!seen_per_operand.insert(std::make_tuple(mode, entry, operand))
|
|
.second) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, &inst)
|
|
<< lookupMode(mode)
|
|
<< " execution mode must not be specified multiple times for "
|
|
"the same entry point and operands";
|
|
}
|
|
}
|
|
}
|
|
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
} // namespace val
|
|
} // namespace spvtools
|