mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-12-27 18:30:14 +00:00
d5766f2801
Now that WebGPU ingests WGSL instead of SPIR-V, there is no need to be so strict about the memory model. Allow any memory model that is already allowed by Vulkan 1.0, either directly or via an existing.
544 lines
23 KiB
C++
544 lines
23 KiB
C++
// 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.
|
|
//
|
|
#include "source/val/validate.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "source/opcode.h"
|
|
#include "source/spirv_target_env.h"
|
|
#include "source/val/instruction.h"
|
|
#include "source/val/validation_state.h"
|
|
|
|
namespace spvtools {
|
|
namespace val {
|
|
namespace {
|
|
|
|
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 || SpvOpFunction != 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 SpvExecutionModel execution_model =
|
|
inst->GetOperandAs<SpvExecutionModel>(0);
|
|
if (execution_model != SpvExecutionModelKernel) {
|
|
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)
|
|
<< "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 || SpvOpTypeVoid != return_type->opcode()) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "OpEntryPoint Entry Point <id> '" << _.getIdName(entry_point_id)
|
|
<< "'s function return type is not void.";
|
|
}
|
|
|
|
const auto* execution_modes = _.GetExecutionModes(entry_point_id);
|
|
if (_.HasCapability(SpvCapabilityShader)) {
|
|
switch (execution_model) {
|
|
case SpvExecutionModelFragment:
|
|
if (execution_modes &&
|
|
execution_modes->count(SpvExecutionModeOriginUpperLeft) &&
|
|
execution_modes->count(SpvExecutionModeOriginLowerLeft)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Fragment execution model entry points can only specify "
|
|
"one of OriginUpperLeft or OriginLowerLeft execution "
|
|
"modes.";
|
|
}
|
|
if (!execution_modes ||
|
|
(!execution_modes->count(SpvExecutionModeOriginUpperLeft) &&
|
|
!execution_modes->count(SpvExecutionModeOriginLowerLeft))) {
|
|
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 SpvExecutionMode& mode) {
|
|
switch (mode) {
|
|
case SpvExecutionModeDepthGreater:
|
|
case SpvExecutionModeDepthLess:
|
|
case SpvExecutionModeDepthUnchanged:
|
|
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 SpvExecutionMode& mode) {
|
|
switch (mode) {
|
|
case SpvExecutionModePixelInterlockOrderedEXT:
|
|
case SpvExecutionModePixelInterlockUnorderedEXT:
|
|
case SpvExecutionModeSampleInterlockOrderedEXT:
|
|
case SpvExecutionModeSampleInterlockUnorderedEXT:
|
|
case SpvExecutionModeShadingRateInterlockOrderedEXT:
|
|
case SpvExecutionModeShadingRateInterlockUnorderedEXT:
|
|
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.";
|
|
}
|
|
break;
|
|
case SpvExecutionModelTessellationControl:
|
|
case SpvExecutionModelTessellationEvaluation:
|
|
if (execution_modes &&
|
|
1 < std::count_if(execution_modes->begin(), execution_modes->end(),
|
|
[](const SpvExecutionMode& mode) {
|
|
switch (mode) {
|
|
case SpvExecutionModeSpacingEqual:
|
|
case SpvExecutionModeSpacingFractionalEven:
|
|
case SpvExecutionModeSpacingFractionalOdd:
|
|
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 SpvExecutionMode& mode) {
|
|
switch (mode) {
|
|
case SpvExecutionModeTriangles:
|
|
case SpvExecutionModeQuads:
|
|
case SpvExecutionModeIsolines:
|
|
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 SpvExecutionMode& mode) {
|
|
switch (mode) {
|
|
case SpvExecutionModeVertexOrderCw:
|
|
case SpvExecutionModeVertexOrderCcw:
|
|
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 SpvExecutionModelGeometry:
|
|
if (!execution_modes ||
|
|
1 != std::count_if(execution_modes->begin(), execution_modes->end(),
|
|
[](const SpvExecutionMode& mode) {
|
|
switch (mode) {
|
|
case SpvExecutionModeInputPoints:
|
|
case SpvExecutionModeInputLines:
|
|
case SpvExecutionModeInputLinesAdjacency:
|
|
case SpvExecutionModeTriangles:
|
|
case SpvExecutionModeInputTrianglesAdjacency:
|
|
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 SpvExecutionMode& mode) {
|
|
switch (mode) {
|
|
case SpvExecutionModeOutputPoints:
|
|
case SpvExecutionModeOutputLineStrip:
|
|
case SpvExecutionModeOutputTriangleStrip:
|
|
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;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (spvIsVulkanEnv(_.context()->target_env)) {
|
|
switch (execution_model) {
|
|
case SpvExecutionModelGLCompute:
|
|
if (!execution_modes ||
|
|
!execution_modes->count(SpvExecutionModeLocalSize)) {
|
|
bool ok = false;
|
|
for (auto& i : _.ordered_instructions()) {
|
|
if (i.opcode() == SpvOpDecorate) {
|
|
if (i.operands().size() > 2) {
|
|
if (i.GetOperandAs<SpvDecoration>(1) == SpvDecorationBuiltIn &&
|
|
i.GetOperandAs<SpvBuiltIn>(2) == SpvBuiltInWorkgroupSize) {
|
|
ok = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!ok) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "In the Vulkan environment, GLCompute execution model "
|
|
"entry points require either the LocalSize execution "
|
|
"mode or an object decorated with WorkgroupSize must be "
|
|
"specified.";
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
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<SpvExecutionMode>(1);
|
|
if (inst->opcode() == SpvOpExecutionModeId) {
|
|
size_t operand_count = inst->operands().size();
|
|
for (size_t i = 2; i < operand_count; ++i) {
|
|
const auto operand_id = inst->GetOperandAs<uint32_t>(2);
|
|
const auto* operand_inst = _.FindDef(operand_id);
|
|
if (mode == SpvExecutionModeSubgroupsPerWorkgroupId ||
|
|
mode == SpvExecutionModeLocalSizeHintId ||
|
|
mode == SpvExecutionModeLocalSizeId) {
|
|
if (!spvOpcodeIsConstant(operand_inst->opcode())) {
|
|
return _.diag(SPV_ERROR_INVALID_ID, inst)
|
|
<< "For OpExecutionModeId all Extra Operand ids must be "
|
|
"constant "
|
|
"instructions.";
|
|
}
|
|
} else {
|
|
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.";
|
|
}
|
|
}
|
|
} else if (mode == SpvExecutionModeSubgroupsPerWorkgroupId ||
|
|
mode == SpvExecutionModeLocalSizeHintId ||
|
|
mode == SpvExecutionModeLocalSizeId) {
|
|
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 auto* models = _.GetExecutionModels(entry_point_id);
|
|
switch (mode) {
|
|
case SpvExecutionModeInvocations:
|
|
case SpvExecutionModeInputPoints:
|
|
case SpvExecutionModeInputLines:
|
|
case SpvExecutionModeInputLinesAdjacency:
|
|
case SpvExecutionModeInputTrianglesAdjacency:
|
|
case SpvExecutionModeOutputLineStrip:
|
|
case SpvExecutionModeOutputTriangleStrip:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const SpvExecutionModel& model) {
|
|
return model == SpvExecutionModelGeometry;
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Geometry execution "
|
|
"model.";
|
|
}
|
|
break;
|
|
case SpvExecutionModeOutputPoints:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[&_](const SpvExecutionModel& model) {
|
|
switch (model) {
|
|
case SpvExecutionModelGeometry:
|
|
return true;
|
|
case SpvExecutionModelMeshNV:
|
|
return _.HasCapability(SpvCapabilityMeshShadingNV);
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Geometry or "
|
|
"MeshNV execution model.";
|
|
} else {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Geometry "
|
|
"execution "
|
|
"model.";
|
|
}
|
|
}
|
|
break;
|
|
case SpvExecutionModeSpacingEqual:
|
|
case SpvExecutionModeSpacingFractionalEven:
|
|
case SpvExecutionModeSpacingFractionalOdd:
|
|
case SpvExecutionModeVertexOrderCw:
|
|
case SpvExecutionModeVertexOrderCcw:
|
|
case SpvExecutionModePointMode:
|
|
case SpvExecutionModeQuads:
|
|
case SpvExecutionModeIsolines:
|
|
if (!std::all_of(
|
|
models->begin(), models->end(),
|
|
[](const SpvExecutionModel& model) {
|
|
return (model == SpvExecutionModelTessellationControl) ||
|
|
(model == SpvExecutionModelTessellationEvaluation);
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a tessellation "
|
|
"execution model.";
|
|
}
|
|
break;
|
|
case SpvExecutionModeTriangles:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const SpvExecutionModel& model) {
|
|
switch (model) {
|
|
case SpvExecutionModelGeometry:
|
|
case SpvExecutionModelTessellationControl:
|
|
case SpvExecutionModelTessellationEvaluation:
|
|
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 SpvExecutionModeOutputVertices:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[&_](const SpvExecutionModel& model) {
|
|
switch (model) {
|
|
case SpvExecutionModelGeometry:
|
|
case SpvExecutionModelTessellationControl:
|
|
case SpvExecutionModelTessellationEvaluation:
|
|
return true;
|
|
case SpvExecutionModelMeshNV:
|
|
return _.HasCapability(SpvCapabilityMeshShadingNV);
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a Geometry, "
|
|
"tessellation or MeshNV execution model.";
|
|
} else {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a Geometry or "
|
|
"tessellation execution model.";
|
|
}
|
|
}
|
|
break;
|
|
case SpvExecutionModePixelCenterInteger:
|
|
case SpvExecutionModeOriginUpperLeft:
|
|
case SpvExecutionModeOriginLowerLeft:
|
|
case SpvExecutionModeEarlyFragmentTests:
|
|
case SpvExecutionModeDepthReplacing:
|
|
case SpvExecutionModeDepthGreater:
|
|
case SpvExecutionModeDepthLess:
|
|
case SpvExecutionModeDepthUnchanged:
|
|
case SpvExecutionModePixelInterlockOrderedEXT:
|
|
case SpvExecutionModePixelInterlockUnorderedEXT:
|
|
case SpvExecutionModeSampleInterlockOrderedEXT:
|
|
case SpvExecutionModeSampleInterlockUnorderedEXT:
|
|
case SpvExecutionModeShadingRateInterlockOrderedEXT:
|
|
case SpvExecutionModeShadingRateInterlockUnorderedEXT:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const SpvExecutionModel& model) {
|
|
return model == SpvExecutionModelFragment;
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Fragment execution "
|
|
"model.";
|
|
}
|
|
break;
|
|
case SpvExecutionModeLocalSizeHint:
|
|
case SpvExecutionModeVecTypeHint:
|
|
case SpvExecutionModeContractionOff:
|
|
case SpvExecutionModeLocalSizeHintId:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[](const SpvExecutionModel& model) {
|
|
return model == SpvExecutionModelKernel;
|
|
})) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with the Kernel execution "
|
|
"model.";
|
|
}
|
|
break;
|
|
case SpvExecutionModeLocalSize:
|
|
case SpvExecutionModeLocalSizeId:
|
|
if (!std::all_of(models->begin(), models->end(),
|
|
[&_](const SpvExecutionModel& model) {
|
|
switch (model) {
|
|
case SpvExecutionModelKernel:
|
|
case SpvExecutionModelGLCompute:
|
|
return true;
|
|
case SpvExecutionModelTaskNV:
|
|
case SpvExecutionModelMeshNV:
|
|
return _.HasCapability(SpvCapabilityMeshShadingNV);
|
|
default:
|
|
return false;
|
|
}
|
|
})) {
|
|
if (_.HasCapability(SpvCapabilityMeshShadingNV)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode can only be used with a Kernel, GLCompute, "
|
|
"MeshNV, or TaskNV 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 (spvIsVulkanEnv(_.context()->target_env)) {
|
|
if (mode == SpvExecutionModeOriginLowerLeft) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "In the Vulkan environment, the OriginLowerLeft execution mode "
|
|
"must not be used.";
|
|
}
|
|
if (mode == SpvExecutionModePixelCenterInteger) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "In the Vulkan environment, the PixelCenterInteger execution "
|
|
"mode must not be used.";
|
|
}
|
|
}
|
|
|
|
if (spvIsWebGPUEnv(_.context()->target_env)) {
|
|
if (mode != SpvExecutionModeOriginUpperLeft &&
|
|
mode != SpvExecutionModeDepthReplacing &&
|
|
mode != SpvExecutionModeDepthGreater &&
|
|
mode != SpvExecutionModeDepthLess &&
|
|
mode != SpvExecutionModeDepthUnchanged &&
|
|
mode != SpvExecutionModeLocalSize &&
|
|
mode != SpvExecutionModeLocalSizeHint) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Execution mode must be one of OriginUpperLeft, "
|
|
"DepthReplacing, DepthGreater, DepthLess, DepthUnchanged, "
|
|
"LocalSize, or LocalSizeHint for WebGPU environment.";
|
|
}
|
|
}
|
|
|
|
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() != SpvMemoryModelVulkanKHR &&
|
|
_.HasCapability(SpvCapabilityVulkanMemoryModelKHR)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "VulkanMemoryModelKHR capability must only be specified if "
|
|
"the VulkanKHR memory model is used.";
|
|
}
|
|
|
|
if (spvIsWebGPUEnv(_.context()->target_env)) {
|
|
if (_.addressing_model() != SpvAddressingModelLogical) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Addressing model must be Logical for WebGPU environment.";
|
|
}
|
|
}
|
|
|
|
if (spvIsOpenCLEnv(_.context()->target_env)) {
|
|
if ((_.addressing_model() != SpvAddressingModelPhysical32) &&
|
|
(_.addressing_model() != SpvAddressingModelPhysical64)) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Addressing model must be Physical32 or Physical64 "
|
|
<< "in the OpenCL environment.";
|
|
}
|
|
if (_.memory_model() != SpvMemoryModelOpenCL) {
|
|
return _.diag(SPV_ERROR_INVALID_DATA, inst)
|
|
<< "Memory model must be OpenCL in the OpenCL environment.";
|
|
}
|
|
}
|
|
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
spv_result_t ModeSettingPass(ValidationState_t& _, const Instruction* inst) {
|
|
switch (inst->opcode()) {
|
|
case SpvOpEntryPoint:
|
|
if (auto error = ValidateEntryPoint(_, inst)) return error;
|
|
break;
|
|
case SpvOpExecutionMode:
|
|
case SpvOpExecutionModeId:
|
|
if (auto error = ValidateExecutionMode(_, inst)) return error;
|
|
break;
|
|
case SpvOpMemoryModel:
|
|
if (auto error = ValidateMemoryModel(_, inst)) return error;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return SPV_SUCCESS;
|
|
}
|
|
|
|
} // namespace val
|
|
} // namespace spvtools
|