Add pass to inject code for robust-buffer-access semantics (#2771)
spirv-opt: Add --graphics-robust-access
Clamps access chain indices so they are always
in bounds.
Assumes:
- Logical addressing mode
- No runtime-array-descriptor-indexing
- No variable pointers
Adds stub code for clamping coordinate and samples
for OpImageTexelPointer.
Adds SinglePassRunAndFail optimizer test fixture.
Android.mk: add source/opt/graphics_robust_access_pass.cpp
Adds Constant::GetSignExtendedValue, Constant::GetZeroExtendedValue
diff --git a/Android.mk b/Android.mk
index 82d9776..8a507da 100644
--- a/Android.mk
+++ b/Android.mk
@@ -110,6 +110,7 @@
source/opt/freeze_spec_constant_value_pass.cpp \
source/opt/function.cpp \
source/opt/generate_webgpu_initializers_pass.cpp \
+ source/opt/graphics_robust_access_pass.cpp \
source/opt/if_conversion.cpp \
source/opt/inline_pass.cpp \
source/opt/inline_exhaustive_pass.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 70772b9..90d80e5 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -521,6 +521,8 @@
"source/opt/function.h",
"source/opt/generate_webgpu_initializers_pass.cpp",
"source/opt/generate_webgpu_initializers_pass.h",
+ "source/opt/graphics_robust_access_pass.cpp",
+ "source/opt/graphics_robust_access_pass.h",
"source/opt/if_conversion.cpp",
"source/opt/if_conversion.h",
"source/opt/inline_exhaustive_pass.cpp",
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index 4c668b4..a52dcd0 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -763,6 +763,27 @@
// continue-targets to legalize for WebGPU.
Optimizer::PassToken CreateSplitInvalidUnreachablePass();
+// Creates a graphics robust access pass.
+//
+// This pass injects code to clamp indexed accesses to buffers and internal
+// arrays, providing guarantees satisfying Vulkan's robustBufferAccess rules.
+//
+// TODO(dneto): Clamps coordinates and sample index for pointer calculations
+// into storage images (OpImageTexelPointer). For an cube array image, it
+// assumes the maximum layer count times 6 is at most 0xffffffff.
+//
+// NOTE: This pass will fail with a message if:
+// - The module is not a Shader module.
+// - The module declares VariablePointers, VariablePointersStorageBuffer, or
+// RuntimeDescriptorArrayEXT capabilities.
+// - The module uses an addressing model other than Logical
+// - Access chain indices are wider than 64 bits.
+// - Access chain index for a struct is not an OpConstant integer or is out
+// of range. (The module is already invalid if that is the case.)
+// - TODO(dneto): The OpImageTexelPointer coordinate component is not 32-bits
+// wide.
+Optimizer::PassToken CreateGraphicsRobustAccessPass();
+
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 3e1280e..278f794 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -48,6 +48,7 @@
freeze_spec_constant_value_pass.h
function.h
generate_webgpu_initializers_pass.h
+ graphics_robust_access_pass.h
if_conversion.h
inline_exhaustive_pass.h
inline_opaque_pass.h
@@ -147,6 +148,7 @@
fold_spec_constant_op_and_composite_pass.cpp
freeze_spec_constant_value_pass.cpp
function.cpp
+ graphics_robust_access_pass.cpp
generate_webgpu_initializers_pass.cpp
if_conversion.cpp
inline_exhaustive_pass.cpp
diff --git a/source/opt/constants.cpp b/source/opt/constants.cpp
index 3c05f9e..b5875c9 100644
--- a/source/opt/constants.cpp
+++ b/source/opt/constants.cpp
@@ -103,6 +103,45 @@
}
}
+uint64_t Constant::GetZeroExtendedValue() const {
+ const auto* int_type = type()->AsInteger();
+ assert(int_type != nullptr);
+ const auto width = int_type->width();
+ assert(width <= 64);
+
+ uint64_t value = 0;
+ if (const IntConstant* ic = AsIntConstant()) {
+ if (width <= 32) {
+ value = ic->GetU32BitValue();
+ } else {
+ value = ic->GetU64BitValue();
+ }
+ } else {
+ assert(AsNullConstant() && "Must be an integer constant.");
+ }
+ return value;
+}
+
+int64_t Constant::GetSignExtendedValue() const {
+ const auto* int_type = type()->AsInteger();
+ assert(int_type != nullptr);
+ const auto width = int_type->width();
+ assert(width <= 64);
+
+ int64_t value = 0;
+ if (const IntConstant* ic = AsIntConstant()) {
+ if (width <= 32) {
+ // Let the C++ compiler do the sign extension.
+ value = int64_t(ic->GetS32BitValue());
+ } else {
+ value = ic->GetS64BitValue();
+ }
+ } else {
+ assert(AsNullConstant() && "Must be an integer constant.");
+ }
+ return value;
+}
+
ConstantManager::ConstantManager(IRContext* ctx) : ctx_(ctx) {
// Populate the constant table with values from constant declarations in the
// module. The values of each OpConstant declaration is the identity
diff --git a/source/opt/constants.h b/source/opt/constants.h
index a8e0fb5..7b9f248 100644
--- a/source/opt/constants.h
+++ b/source/opt/constants.h
@@ -116,6 +116,14 @@
// Integer type.
int64_t GetS64() const;
+ // Returns the zero-extended representation of an integer constant. Must
+ // be an integral constant of at most 64 bits.
+ uint64_t GetZeroExtendedValue() const;
+
+ // Returns the sign-extended representation of an integer constant. Must
+ // be an integral constant of at most 64 bits.
+ int64_t GetSignExtendedValue() const;
+
// Returns true if the constant is a zero or a composite containing 0s.
virtual bool IsZero() const { return false; }
diff --git a/source/opt/graphics_robust_access_pass.cpp b/source/opt/graphics_robust_access_pass.cpp
new file mode 100644
index 0000000..dd60e8c
--- /dev/null
+++ b/source/opt/graphics_robust_access_pass.cpp
@@ -0,0 +1,968 @@
+// Copyright (c) 2019 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.
+
+// This pass injects code in a graphics shader to implement guarantees
+// satisfying Vulkan's robustBufferAcces rules. Robust access rules permit
+// an out-of-bounds access to be redirected to an access of the same type
+// (load, store, etc.) but within the same root object.
+//
+// We assume baseline functionality in Vulkan, i.e. the module uses
+// logical addressing mode, without VK_KHR_variable_pointers.
+//
+// - Logical addressing mode implies:
+// - Each root pointer (a pointer that exists other than by the
+// execution of a shader instruction) is the result of an OpVariable.
+//
+// - Instructions that result in pointers are:
+// OpVariable
+// OpAccessChain
+// OpInBoundsAccessChain
+// OpFunctionParameter
+// OpImageTexelPointer
+// OpCopyObject
+//
+// - Instructions that use a pointer are:
+// OpLoad
+// OpStore
+// OpAccessChain
+// OpInBoundsAccessChain
+// OpFunctionCall
+// OpImageTexelPointer
+// OpCopyMemory
+// OpCopyObject
+// all OpAtomic* instructions
+//
+// We classify pointer-users into:
+// - Accesses:
+// - OpLoad
+// - OpStore
+// - OpAtomic*
+// - OpCopyMemory
+//
+// - Address calculations:
+// - OpAccessChain
+// - OpInBoundsAccessChain
+//
+// - Pass-through:
+// - OpFunctionCall
+// - OpFunctionParameter
+// - OpCopyObject
+//
+// The strategy is:
+//
+// - Handle only logical addressing mode. In particular, don't handle a module
+// if it uses one of the variable-pointers capabilities.
+//
+// - Don't handle modules using capability RuntimeDescriptorArrayEXT. So the
+// only runtime arrays are those that are the last member in a
+// Block-decorated struct. This allows us to feasibly/easily compute the
+// length of the runtime array. See below.
+//
+// - The memory locations accessed by OpLoad, OpStore, OpCopyMemory, and
+// OpAtomic* are determined by their pointer parameter or parameters.
+// Pointers are always (correctly) typed and so the address and number of
+// consecutive locations are fully determined by the pointer.
+//
+// - A pointer value orginates as one of few cases:
+//
+// - OpVariable for an interface object or an array of them: image,
+// buffer (UBO or SSBO), sampler, sampled-image, push-constant, input
+// variable, output variable. The execution environment is responsible for
+// allocating the correct amount of storage for these, and for ensuring
+// each resource bound to such a variable is big enough to contain the
+// SPIR-V pointee type of the variable.
+//
+// - OpVariable for a non-interface object. These are variables in
+// Workgroup, Private, and Function storage classes. The compiler ensures
+// the underlying allocation is big enough to store the entire SPIR-V
+// pointee type of the variable.
+//
+// - An OpFunctionParameter. This always maps to a pointer parameter to an
+// OpFunctionCall.
+//
+// - In logical addressing mode, these are severely limited:
+// "Any pointer operand to an OpFunctionCall must be:
+// - a memory object declaration, or
+// - a pointer to an element in an array that is a memory object
+// declaration, where the element type is OpTypeSampler or OpTypeImage"
+//
+// - This has an important simplifying consequence:
+//
+// - When looking for a pointer to the structure containing a runtime
+// array, you begin with a pointer to the runtime array and trace
+// backward in the function. You never have to trace back beyond
+// your function call boundary. So you can't take a partial access
+// chain into an SSBO, then pass that pointer into a function. So
+// we don't resort to using fat pointers to compute array length.
+// We can trace back to a pointer to the containing structure,
+// and use that in an OpArrayLength instruction. (The structure type
+// gives us the member index of the runtime array.)
+//
+// - Otherwise, the pointer type fully encodes the range of valid
+// addresses. In particular, the type of a pointer to an aggregate
+// value fully encodes the range of indices when indexing into
+// that aggregate.
+//
+// - The pointer is the result of an access chain instruction. We clamp
+// indices contributing to address calculations. As noted above, the
+// valid ranges are either bound by the length of a runtime array, or
+// by the type of the base pointer. The length of a runtime array is
+// the result of an OpArrayLength instruction acting on the pointer of
+// the containing structure as noted above.
+//
+// - TODO(dneto): OpImageTexelPointer:
+// - Clamp coordinate to the image size returned by OpImageQuerySize
+// - If multi-sampled, clamp the sample index to the count returned by
+// OpImageQuerySamples.
+// - If not multi-sampled, set the sample index to 0.
+//
+// - Rely on the external validator to check that pointers are only
+// used by the instructions as above.
+//
+// - Handles OpTypeRuntimeArray
+// Track pointer back to original resource (pointer to struct), so we can
+// query the runtime array size.
+//
+
+#include "graphics_robust_access_pass.h"
+
+#include <algorithm>
+#include <cstring>
+#include <functional>
+#include <initializer_list>
+#include <utility>
+
+#include "constants.h"
+#include "def_use_manager.h"
+#include "function.h"
+#include "ir_context.h"
+#include "module.h"
+#include "pass.h"
+#include "source/diagnostic.h"
+#include "source/util/make_unique.h"
+#include "spirv-tools/libspirv.h"
+#include "spirv/unified1/GLSL.std.450.h"
+#include "spirv/unified1/spirv.h"
+#include "type_manager.h"
+#include "types.h"
+
+namespace spvtools {
+namespace opt {
+
+using opt::BasicBlock;
+using opt::Instruction;
+using opt::Operand;
+using spvtools::MakeUnique;
+
+GraphicsRobustAccessPass::GraphicsRobustAccessPass() : module_status_() {}
+
+Pass::Status GraphicsRobustAccessPass::Process() {
+ module_status_ = PerModuleState();
+
+ ProcessCurrentModule();
+
+ auto result = module_status_.failed
+ ? Status::Failure
+ : (module_status_.modified ? Status::SuccessWithChange
+ : Status::SuccessWithoutChange);
+
+ return result;
+}
+
+spvtools::DiagnosticStream GraphicsRobustAccessPass::Fail() {
+ module_status_.failed = true;
+ // We don't really have a position, and we'll ignore the result.
+ return std::move(
+ spvtools::DiagnosticStream({}, consumer(), "", SPV_ERROR_INVALID_BINARY)
+ << name() << ": ");
+}
+
+spv_result_t GraphicsRobustAccessPass::IsCompatibleModule() {
+ auto* feature_mgr = context()->get_feature_mgr();
+ if (!feature_mgr->HasCapability(SpvCapabilityShader))
+ return Fail() << "Can only process Shader modules";
+ if (feature_mgr->HasCapability(SpvCapabilityVariablePointers))
+ return Fail() << "Can't process modules with VariablePointers capability";
+ if (feature_mgr->HasCapability(SpvCapabilityVariablePointersStorageBuffer))
+ return Fail() << "Can't process modules with VariablePointersStorageBuffer "
+ "capability";
+ if (feature_mgr->HasCapability(SpvCapabilityRuntimeDescriptorArrayEXT)) {
+ // These have a RuntimeArray outside of Block-decorated struct. There
+ // is no way to compute the array length from within SPIR-V.
+ return Fail() << "Can't process modules with RuntimeDescriptorArrayEXT "
+ "capability";
+ }
+
+ {
+ auto* inst = context()->module()->GetMemoryModel();
+ const auto addressing_model = inst->GetSingleWordOperand(0);
+ if (addressing_model != SpvAddressingModelLogical)
+ return Fail() << "Addressing model must be Logical. Found "
+ << inst->PrettyPrint();
+ }
+ return SPV_SUCCESS;
+}
+
+spv_result_t GraphicsRobustAccessPass::ProcessCurrentModule() {
+ auto err = IsCompatibleModule();
+ if (err != SPV_SUCCESS) return err;
+
+ ProcessFunction fn = [this](opt::Function* f) { return ProcessAFunction(f); };
+ module_status_.modified |= context()->ProcessReachableCallTree(fn);
+
+ // Need something here. It's the price we pay for easier failure paths.
+ return SPV_SUCCESS;
+}
+
+bool GraphicsRobustAccessPass::ProcessAFunction(opt::Function* function) {
+ // Ensure that all pointers computed inside a function are within bounds.
+ // Find the access chains in this block before trying to modify them.
+ std::vector<Instruction*> access_chains;
+ std::vector<Instruction*> image_texel_pointers;
+ for (auto& block : *function) {
+ for (auto& inst : block) {
+ switch (inst.opcode()) {
+ case SpvOpAccessChain:
+ case SpvOpInBoundsAccessChain:
+ access_chains.push_back(&inst);
+ break;
+ case SpvOpImageTexelPointer:
+ image_texel_pointers.push_back(&inst);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ for (auto* inst : access_chains) {
+ ClampIndicesForAccessChain(inst);
+ }
+
+ for (auto* inst : image_texel_pointers) {
+ if (SPV_SUCCESS != ClampCoordinateForImageTexelPointer(inst)) break;
+ }
+ return module_status_.modified;
+}
+
+void GraphicsRobustAccessPass::ClampIndicesForAccessChain(
+ Instruction* access_chain) {
+ Instruction& inst = *access_chain;
+
+ auto* constant_mgr = context()->get_constant_mgr();
+ auto* def_use_mgr = context()->get_def_use_mgr();
+ auto* type_mgr = context()->get_type_mgr();
+
+ // Replaces one of the OpAccessChain index operands with a new value.
+ // Updates def-use analysis.
+ auto replace_index = [&inst, def_use_mgr](uint32_t operand_index,
+ Instruction* new_value) {
+ inst.SetOperand(operand_index, {new_value->result_id()});
+ def_use_mgr->AnalyzeInstUse(&inst);
+ };
+
+ // Replaces one of the OpAccesssChain index operands with a clamped value.
+ // Replace the operand at |operand_index| with the value computed from
+ // unsigned_clamp(%old_value, %min_value, %max_value). It also analyzes
+ // the new instruction and records that them module is modified.
+ auto clamp_index = [&inst, this, &replace_index](
+ uint32_t operand_index, Instruction* old_value,
+ Instruction* min_value, Instruction* max_value) {
+ auto* clamp_inst = MakeClampInst(old_value, min_value, max_value, &inst);
+ replace_index(operand_index, clamp_inst);
+ };
+
+ // Ensures the specified index of access chain |inst| has a value that is
+ // at most |count| - 1. If the index is already a constant value less than
+ // |count| then no change is made.
+ auto clamp_to_literal_count = [&inst, this, &constant_mgr, &type_mgr,
+ &replace_index, &clamp_index](
+ uint32_t operand_index, uint64_t count) {
+ Instruction* index_inst =
+ this->GetDef(inst.GetSingleWordOperand(operand_index));
+ const auto* index_type =
+ type_mgr->GetType(index_inst->type_id())->AsInteger();
+ assert(index_type);
+ if (count <= 1) {
+ // Replace the index with 0.
+ replace_index(operand_index, GetValueForType(0, index_type));
+ return;
+ }
+
+ const auto index_width = index_type->width();
+
+ // If the index is a constant then |index_constant| will not be a null
+ // pointer. (If index is an |OpConstantNull| then it |index_constant| will
+ // not be a null pointer.) Since access chain indices must be scalar
+ // integers, this can't be a spec constant.
+ if (auto* index_constant = constant_mgr->GetConstantFromInst(index_inst)) {
+ auto* int_index_constant = index_constant->AsIntConstant();
+ int64_t value = 0;
+ // OpAccessChain indices are treated as signed. So get the signed
+ // constant value here.
+ if (index_width <= 32) {
+ value = int64_t(int_index_constant->GetS32BitValue());
+ } else if (index_width <= 64) {
+ value = int_index_constant->GetS64BitValue();
+ } else {
+ this->Fail() << "Can't handle indices wider than 64 bits, found "
+ "constant index with "
+ << index_type->width() << "bits";
+ return;
+ }
+ if (value < 0) {
+ replace_index(operand_index, GetValueForType(0, index_type));
+ } else if (uint64_t(value) < count) {
+ // Nothing to do.
+ return;
+ } else {
+ // Replace with count - 1.
+ assert(count > 0); // Already took care of this case above.
+ replace_index(operand_index, GetValueForType(count - 1, index_type));
+ }
+ } else {
+ // Generate a clamp instruction.
+
+ // Compute the bit width of a viable type to hold (count-1).
+ const auto maxval = count - 1;
+ const auto* maxval_type = index_type;
+ // Look for a bit width, up to 64 bits wide, to fit maxval.
+ uint32_t maxval_width = index_width;
+ while ((maxval_width < 64) && (0 != (maxval >> maxval_width))) {
+ maxval_width *= 2;
+ }
+ // Widen the index value if necessary
+ if (maxval_width > index_width) {
+ // Find the wider type. We only need this case if a constant (array)
+ // bound is too big. This never requires us to *add* a capability
+ // declaration for Int64 because the existence of the array bound would
+ // already have required that declaration.
+ index_inst = WidenInteger(index_type->IsSigned(), maxval_width,
+ index_inst, &inst);
+ maxval_type = type_mgr->GetType(index_inst->type_id())->AsInteger();
+ }
+ // Finally, clamp the index.
+ clamp_index(operand_index, index_inst, GetValueForType(0, maxval_type),
+ GetValueForType(maxval, maxval_type));
+ }
+ };
+
+ // Ensures the specified index of access chain |inst| has a value that is at
+ // most the value of |count_inst| minus 1, where |count_inst| is treated as an
+ // unsigned integer.
+ auto clamp_to_count = [&inst, this, &constant_mgr, &clamp_to_literal_count,
+ &clamp_index, &type_mgr](uint32_t operand_index,
+ Instruction* count_inst) {
+ Instruction* index_inst =
+ this->GetDef(inst.GetSingleWordOperand(operand_index));
+ const auto* index_type =
+ type_mgr->GetType(index_inst->type_id())->AsInteger();
+ const auto* count_type =
+ type_mgr->GetType(count_inst->type_id())->AsInteger();
+ assert(index_type);
+ if (const auto* count_constant =
+ constant_mgr->GetConstantFromInst(count_inst)) {
+ uint64_t value = 0;
+ const auto width = count_constant->type()->AsInteger()->width();
+ if (width <= 32) {
+ value = count_constant->AsIntConstant()->GetU32BitValue();
+ } else if (width <= 64) {
+ value = count_constant->AsIntConstant()->GetU64BitValue();
+ } else {
+ this->Fail() << "Can't handle indices wider than 64 bits, found "
+ "constant index with "
+ << index_type->width() << "bits";
+ return;
+ }
+ clamp_to_literal_count(operand_index, value);
+ } else {
+ // Widen them to the same width.
+ const auto index_width = index_type->width();
+ const auto count_width = count_type->width();
+ const auto target_width = std::max(index_width, count_width);
+ // UConvert requires the result type to have 0 signedness. So enforce
+ // that here.
+ auto* wider_type = index_width < count_width ? count_type : index_type;
+ if (index_type->width() < target_width) {
+ // Access chain indices are treated as signed integers.
+ index_inst = WidenInteger(true, target_width, index_inst, &inst);
+ } else if (count_type->width() < target_width) {
+ // Assume type sizes are treated as unsigned.
+ count_inst = WidenInteger(false, target_width, count_inst, &inst);
+ }
+ // Compute count - 1.
+ // It doesn't matter if 1 is signed or unsigned.
+ auto* one = GetValueForType(1, wider_type);
+ auto* count_minus_1 = InsertInst(
+ &inst, SpvOpISub, type_mgr->GetId(wider_type), TakeNextId(),
+ {{SPV_OPERAND_TYPE_ID, {count_inst->result_id()}},
+ {SPV_OPERAND_TYPE_ID, {one->result_id()}}});
+ clamp_index(operand_index, index_inst, GetValueForType(0, wider_type),
+ count_minus_1);
+ }
+ };
+
+ const Instruction* base_inst = GetDef(inst.GetSingleWordInOperand(0));
+ const Instruction* base_type = GetDef(base_inst->type_id());
+ Instruction* pointee_type = GetDef(base_type->GetSingleWordInOperand(1));
+
+ // Walk the indices from earliest to latest, replacing indices with a
+ // clamped value, and updating the pointee_type. The order matters for
+ // the case when we have to compute the length of a runtime array. In
+ // that the algorithm relies on the fact that that the earlier indices
+ // have already been clamped.
+ const uint32_t num_operands = inst.NumOperands();
+ for (uint32_t idx = 3; !module_status_.failed && idx < num_operands; ++idx) {
+ const uint32_t index_id = inst.GetSingleWordOperand(idx);
+ Instruction* index_inst = GetDef(index_id);
+
+ switch (pointee_type->opcode()) {
+ case SpvOpTypeMatrix: // Use column count
+ case SpvOpTypeVector: // Use component count
+ {
+ const uint32_t count = pointee_type->GetSingleWordOperand(2);
+ clamp_to_literal_count(idx, count);
+ pointee_type = GetDef(pointee_type->GetSingleWordOperand(1));
+ } break;
+
+ case SpvOpTypeArray: {
+ // The array length can be a spec constant, so go through the general
+ // case.
+ Instruction* array_len = GetDef(pointee_type->GetSingleWordOperand(2));
+ clamp_to_count(idx, array_len);
+ pointee_type = GetDef(pointee_type->GetSingleWordOperand(1));
+ } break;
+
+ case SpvOpTypeStruct: {
+ // SPIR-V requires the index to be an OpConstant.
+ // We need to know the index literal value so we can compute the next
+ // pointee type.
+ if (index_inst->opcode() != SpvOpConstant ||
+ !constant_mgr->GetConstantFromInst(index_inst)
+ ->type()
+ ->AsInteger()) {
+ Fail() << "Member index into struct is not a constant integer: "
+ << index_inst->PrettyPrint(
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
+ << "\nin access chain: "
+ << inst.PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ return;
+ }
+ const auto num_members = pointee_type->NumInOperands();
+ const auto* index_constant =
+ constant_mgr->GetConstantFromInst(index_inst);
+ // Get the sign-extended value, since access index is always treated as
+ // signed.
+ const auto index_value = index_constant->GetSignExtendedValue();
+ if (index_value < 0 || index_value >= num_members) {
+ Fail() << "Member index " << index_value
+ << " is out of bounds for struct type: "
+ << pointee_type->PrettyPrint(
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES)
+ << "\nin access chain: "
+ << inst.PrettyPrint(SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ return;
+ }
+ pointee_type = GetDef(pointee_type->GetSingleWordInOperand(
+ static_cast<uint32_t>(index_value)));
+ // No need to clamp this index. We just checked that it's valid.
+ } break;
+
+ case SpvOpTypeRuntimeArray: {
+ auto* array_len = MakeRuntimeArrayLengthInst(&inst, idx);
+ if (!array_len) { // We've already signaled an error.
+ return;
+ }
+ clamp_to_count(idx, array_len);
+ pointee_type = GetDef(pointee_type->GetSingleWordOperand(1));
+ } break;
+
+ default:
+ Fail() << " Unhandled pointee type for access chain "
+ << pointee_type->PrettyPrint(
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ }
+ }
+}
+
+uint32_t GraphicsRobustAccessPass::GetGlslInsts() {
+ if (module_status_.glsl_insts_id == 0) {
+ // This string serves double-duty as raw data for a string and for a vector
+ // of 32-bit words
+ const char glsl[] = "GLSL.std.450\0\0\0\0";
+ const size_t glsl_str_byte_len = 16;
+ // Use an existing import if we can.
+ for (auto& inst : context()->module()->ext_inst_imports()) {
+ const auto& name_words = inst.GetInOperand(0).words;
+ if (0 == std::strncmp(reinterpret_cast<const char*>(name_words.data()),
+ glsl, glsl_str_byte_len)) {
+ module_status_.glsl_insts_id = inst.result_id();
+ }
+ }
+ if (module_status_.glsl_insts_id == 0) {
+ // Make a new import instruction.
+ module_status_.glsl_insts_id = TakeNextId();
+ std::vector<uint32_t> words(glsl_str_byte_len / sizeof(uint32_t));
+ std::memcpy(words.data(), glsl, glsl_str_byte_len);
+ auto import_inst = MakeUnique<Instruction>(
+ context(), SpvOpExtInstImport, 0, module_status_.glsl_insts_id,
+ std::initializer_list<Operand>{
+ Operand{SPV_OPERAND_TYPE_LITERAL_STRING, std::move(words)}});
+ Instruction* inst = import_inst.get();
+ context()->module()->AddExtInstImport(std::move(import_inst));
+ module_status_.modified = true;
+ context()->AnalyzeDefUse(inst);
+ // Reanalyze the feature list, since we added an extended instruction
+ // set improt.
+ context()->get_feature_mgr()->Analyze(context()->module());
+ }
+ }
+ return module_status_.glsl_insts_id;
+}
+
+opt::Instruction* opt::GraphicsRobustAccessPass::GetValueForType(
+ uint64_t value, const analysis::Integer* type) {
+ auto* mgr = context()->get_constant_mgr();
+ assert(type->width() <= 64);
+ std::vector<uint32_t> words;
+ words.push_back(uint32_t(value));
+ if (type->width() > 32) {
+ words.push_back(uint32_t(value >> 32u));
+ }
+ const auto* constant = mgr->GetConstant(type, words);
+ return mgr->GetDefiningInstruction(
+ constant, context()->get_type_mgr()->GetTypeInstruction(type));
+}
+
+opt::Instruction* opt::GraphicsRobustAccessPass::WidenInteger(
+ bool sign_extend, uint32_t bit_width, Instruction* value,
+ Instruction* before_inst) {
+ analysis::Integer unsigned_type_for_query(bit_width, false);
+ auto* type_mgr = context()->get_type_mgr();
+ auto* unsigned_type = type_mgr->GetRegisteredType(&unsigned_type_for_query);
+ auto type_id = context()->get_type_mgr()->GetId(unsigned_type);
+ auto conversion_id = TakeNextId();
+ auto* conversion = InsertInst(
+ before_inst, (sign_extend ? SpvOpSConvert : SpvOpUConvert), type_id,
+ conversion_id, {{SPV_OPERAND_TYPE_ID, {value->result_id()}}});
+ return conversion;
+}
+
+Instruction* GraphicsRobustAccessPass::MakeClampInst(Instruction* x,
+ Instruction* min,
+ Instruction* max,
+ Instruction* where) {
+ // Get IDs of instructions we'll be referencing. Evaluate them before calling
+ // the function so we force a deterministic ordering in case both of them need
+ // to take a new ID.
+ const uint32_t glsl_insts_id = GetGlslInsts();
+ uint32_t clamp_id = TakeNextId();
+ assert(x->type_id() == min->type_id());
+ assert(x->type_id() == max->type_id());
+ auto* clamp_inst = InsertInst(
+ where, SpvOpExtInst, x->type_id(), clamp_id,
+ {
+ {SPV_OPERAND_TYPE_ID, {glsl_insts_id}},
+ {SPV_OPERAND_TYPE_EXTENSION_INSTRUCTION_NUMBER, {GLSLstd450UClamp}},
+ {SPV_OPERAND_TYPE_ID, {x->result_id()}},
+ {SPV_OPERAND_TYPE_ID, {min->result_id()}},
+ {SPV_OPERAND_TYPE_ID, {max->result_id()}},
+ });
+ return clamp_inst;
+}
+
+Instruction* GraphicsRobustAccessPass::MakeRuntimeArrayLengthInst(
+ Instruction* access_chain, uint32_t operand_index) {
+ // The Index parameter to the access chain at |operand_index| is indexing
+ // *into* the runtime-array. To get the number of elements in the runtime
+ // array we need a pointer to the Block-decorated struct that contains the
+ // runtime array. So conceptually we have to go 2 steps backward in the
+ // access chain. The two steps backward might forces us to traverse backward
+ // across multiple dominating instructions.
+ auto* type_mgr = context()->get_type_mgr();
+
+ // How many access chain indices do we have to unwind to find the pointer
+ // to the struct containing the runtime array?
+ uint32_t steps_remaining = 2;
+ // Find or create an instruction computing the pointer to the structure
+ // containing the runtime array.
+ // Walk backward through pointer address calculations until we either get
+ // to exactly the right base pointer, or to an access chain instruction
+ // that we can replicate but truncate to compute the address of the right
+ // struct.
+ Instruction* current_access_chain = access_chain;
+ Instruction* pointer_to_containing_struct = nullptr;
+ while (steps_remaining > 0) {
+ switch (current_access_chain->opcode()) {
+ case SpvOpCopyObject:
+ // Whoops. Walk right through this one.
+ current_access_chain =
+ GetDef(current_access_chain->GetSingleWordInOperand(0));
+ break;
+ case SpvOpAccessChain:
+ case SpvOpInBoundsAccessChain: {
+ const int first_index_operand = 3;
+ // How many indices in this access chain contribute to getting us
+ // to an element in the runtime array?
+ const auto num_contributing_indices =
+ current_access_chain == access_chain
+ ? operand_index - (first_index_operand - 1)
+ : current_access_chain->NumInOperands() - 1 /* skip the base */;
+ Instruction* base =
+ GetDef(current_access_chain->GetSingleWordInOperand(0));
+ if (num_contributing_indices == steps_remaining) {
+ // The base pointer points to the structure.
+ pointer_to_containing_struct = base;
+ steps_remaining = 0;
+ break;
+ } else if (num_contributing_indices < steps_remaining) {
+ // Peel off the index and keep going backward.
+ steps_remaining -= num_contributing_indices;
+ current_access_chain = base;
+ } else {
+ // This access chain has more indices than needed. Generate a new
+ // access chain instruction, but truncating the list of indices.
+ const int base_operand = 2;
+ // We'll use the base pointer and the indices up to but not including
+ // the one indexing into the runtime array.
+ Instruction::OperandList ops;
+ // Use the base pointer
+ ops.push_back(current_access_chain->GetOperand(base_operand));
+ const uint32_t num_indices_to_keep =
+ num_contributing_indices - steps_remaining - 1;
+ for (uint32_t i = 0; i <= num_indices_to_keep; i++) {
+ ops.push_back(
+ current_access_chain->GetOperand(first_index_operand + i));
+ }
+ // Compute the type of the result of the new access chain. Start at
+ // the base and walk the indices in a forward direction.
+ auto* constant_mgr = context()->get_constant_mgr();
+ std::vector<uint32_t> indices_for_type;
+ for (uint32_t i = 0; i < ops.size() - 1; i++) {
+ uint32_t index_for_type_calculation = 0;
+ Instruction* index =
+ GetDef(current_access_chain->GetSingleWordOperand(
+ first_index_operand + i));
+ if (auto* index_constant =
+ constant_mgr->GetConstantFromInst(index)) {
+ // We only need 32 bits. For the type calculation, it's sufficient
+ // to take the zero-extended value. It only matters for the struct
+ // case, and struct member indices are unsigned.
+ index_for_type_calculation =
+ uint32_t(index_constant->GetZeroExtendedValue());
+ } else {
+ // Indexing into a variably-sized thing like an array. Use 0.
+ index_for_type_calculation = 0;
+ }
+ indices_for_type.push_back(index_for_type_calculation);
+ }
+ auto* base_ptr_type = type_mgr->GetType(base->type_id())->AsPointer();
+ auto* base_pointee_type = base_ptr_type->pointee_type();
+ auto* new_access_chain_result_pointee_type =
+ type_mgr->GetMemberType(base_pointee_type, indices_for_type);
+ const uint32_t new_access_chain_type_id = type_mgr->FindPointerToType(
+ type_mgr->GetId(new_access_chain_result_pointee_type),
+ base_ptr_type->storage_class());
+
+ // Create the instruction and insert it.
+ const auto new_access_chain_id = TakeNextId();
+ auto* new_access_chain =
+ InsertInst(current_access_chain, current_access_chain->opcode(),
+ new_access_chain_type_id, new_access_chain_id, ops);
+ pointer_to_containing_struct = new_access_chain;
+ steps_remaining = 0;
+ break;
+ }
+ } break;
+ default:
+ Fail() << "Unhandled access chain in logical addressing mode passes "
+ "through "
+ << current_access_chain->PrettyPrint(
+ SPV_BINARY_TO_TEXT_OPTION_SHOW_BYTE_OFFSET |
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ return nullptr;
+ }
+ }
+ assert(pointer_to_containing_struct);
+ auto* pointee_type =
+ type_mgr->GetType(pointer_to_containing_struct->type_id())
+ ->AsPointer()
+ ->pointee_type();
+
+ auto* struct_type = pointee_type->AsStruct();
+ const uint32_t member_index_of_runtime_array =
+ uint32_t(struct_type->element_types().size() - 1);
+ // Create the length-of-array instruction before the original access chain,
+ // but after the generation of the pointer to the struct.
+ const auto array_len_id = TakeNextId();
+ analysis::Integer uint_type_for_query(32, false);
+ auto* uint_type = type_mgr->GetRegisteredType(&uint_type_for_query);
+ auto* array_len = InsertInst(
+ access_chain, SpvOpArrayLength, type_mgr->GetId(uint_type), array_len_id,
+ {{SPV_OPERAND_TYPE_ID, {pointer_to_containing_struct->result_id()}},
+ {SPV_OPERAND_TYPE_LITERAL_INTEGER, {member_index_of_runtime_array}}});
+ return array_len;
+}
+
+spv_result_t GraphicsRobustAccessPass::ClampCoordinateForImageTexelPointer(
+ opt::Instruction* image_texel_pointer) {
+ // TODO(dneto): Write tests for this code.
+ return SPV_SUCCESS;
+
+ // Example:
+ // %texel_ptr = OpImageTexelPointer %texel_ptr_type %image_ptr %coord
+ // %sample
+ //
+ // We want to clamp %coord components between vector-0 and the result
+ // of OpImageQuerySize acting on the underlying image. So insert:
+ // %image = OpLoad %image_type %image_ptr
+ // %query_size = OpImageQuerySize %query_size_type %image
+ //
+ // For a multi-sampled image, %sample is the sample index, and we need
+ // to clamp it between zero and the number of samples in the image.
+ // %sample_count = OpImageQuerySamples %uint %image
+ // %max_sample_index = OpISub %uint %sample_count %uint_1
+ // For non-multi-sampled images, the sample index must be constant zero.
+
+ auto* def_use_mgr = context()->get_def_use_mgr();
+ auto* type_mgr = context()->get_type_mgr();
+ auto* constant_mgr = context()->get_constant_mgr();
+
+ auto* image_ptr = GetDef(image_texel_pointer->GetSingleWordInOperand(0));
+ auto* image_ptr_type = GetDef(image_ptr->type_id());
+ auto image_type_id = image_ptr_type->GetSingleWordInOperand(1);
+ auto* image_type = GetDef(image_type_id);
+ auto* coord = GetDef(image_texel_pointer->GetSingleWordInOperand(1));
+ auto* samples = GetDef(image_texel_pointer->GetSingleWordInOperand(2));
+
+ // We will modify the module, at least by adding image query instructions.
+ module_status_.modified = true;
+
+ // Declare the ImageQuery capability if the module doesn't already have it.
+ auto* feature_mgr = context()->get_feature_mgr();
+ if (!feature_mgr->HasCapability(SpvCapabilityImageQuery)) {
+ auto cap = MakeUnique<Instruction>(
+ context(), SpvOpCapability, 0, 0,
+ std::initializer_list<Operand>{
+ {SPV_OPERAND_TYPE_CAPABILITY, {SpvCapabilityImageQuery}}});
+ def_use_mgr->AnalyzeInstDefUse(cap.get());
+ context()->AddCapability(std::move(cap));
+ feature_mgr->Analyze(context()->module());
+ }
+
+ // OpImageTexelPointer is used to translate a coordinate and sample index
+ // into an address for use with an atomic operation. That is, it may only
+ // used with what Vulkan calls a "storage image"
+ // (OpTypeImage parameter Sampled=2).
+ // Note: A storage image never has a level-of-detail associated with it.
+
+ // Constraints on the sample id:
+ // - Only 2D images can be multi-sampled: OpTypeImage parameter MS=1
+ // only if Dim=2D.
+ // - Non-multi-sampled images (OpTypeImage parameter MS=0) must use
+ // sample ID to a constant 0.
+
+ // The coordinate is treated as unsigned, and should be clamped against the
+ // image "size", returned by OpImageQuerySize. (Note: OpImageQuerySizeLod
+ // is only usable with a sampled image, i.e. its image type has Sampled=1).
+
+ // Determine the result type for the OpImageQuerySize.
+ // For non-arrayed images:
+ // non-Cube:
+ // - Always the same as the coordinate type
+ // Cube:
+ // - Use all but the last component of the coordinate (which is the face
+ // index from 0 to 5).
+ // For arrayed images (in Vulkan the Dim is 1D, 2D, or Cube):
+ // non-Cube:
+ // - A vector with the components in the coordinate, and one more for
+ // the layer index.
+ // Cube:
+ // - The same as the coordinate type: 3-element integer vector.
+ // - The third component from the size query is the layer count.
+ // - The third component in the texel pointer calculation is
+ // 6 * layer + face, where 0 <= face < 6.
+ // Cube: Use all but the last component of the coordinate (which is the face
+ // index from 0 to 5).
+ const auto dim = SpvDim(image_type->GetSingleWordInOperand(1));
+ const bool arrayed = image_type->GetSingleWordInOperand(3) == 1;
+ const bool multisampled = image_type->GetSingleWordInOperand(4) != 0;
+ const auto query_num_components = [dim, arrayed, this]() -> int {
+ const int arrayness_bonus = arrayed ? 1 : 0;
+ int num_coords = 0;
+ switch (dim) {
+ case SpvDimBuffer:
+ case SpvDim1D:
+ num_coords = 1;
+ break;
+ case SpvDimCube:
+ // For cube, we need bounds for x, y, but not face.
+ case SpvDimRect:
+ case SpvDim2D:
+ num_coords = 2;
+ break;
+ case SpvDim3D:
+ num_coords = 3;
+ break;
+ case SpvDimSubpassData:
+ case SpvDimMax:
+ return Fail() << "Invalid image dimension for OpImageTexelPointer: "
+ << int(dim);
+ break;
+ }
+ return num_coords + arrayness_bonus;
+ }();
+ const auto* coord_component_type = [type_mgr, coord]() {
+ const analysis::Type* coord_type = type_mgr->GetType(coord->type_id());
+ if (auto* vector_type = coord_type->AsVector()) {
+ return vector_type->element_type()->AsInteger();
+ }
+ return coord_type->AsInteger();
+ }();
+ // For now, only handle 32-bit case for coordinates.
+ if (!coord_component_type) {
+ return Fail() << " Coordinates for OpImageTexelPointer are not integral: "
+ << image_texel_pointer->PrettyPrint(
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ }
+ if (coord_component_type->width() != 32) {
+ return Fail() << " Expected OpImageTexelPointer coordinate components to "
+ "be 32-bits wide. They are "
+ << coord_component_type->width() << " bits. "
+ << image_texel_pointer->PrettyPrint(
+ SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
+ }
+ const auto* query_size_type =
+ [type_mgr, coord_component_type,
+ query_num_components]() -> const analysis::Type* {
+ if (query_num_components == 1) return coord_component_type;
+ analysis::Vector proposed(coord_component_type, query_num_components);
+ return type_mgr->GetRegisteredType(&proposed);
+ }();
+
+ const uint32_t image_id = TakeNextId();
+ auto* image =
+ InsertInst(image_texel_pointer, SpvOpLoad, image_type_id, image_id,
+ {{SPV_OPERAND_TYPE_ID, {image_ptr->result_id()}}});
+
+ const uint32_t query_size_id = TakeNextId();
+ auto* query_size =
+ InsertInst(image_texel_pointer, SpvOpImageQuerySize,
+ type_mgr->GetTypeInstruction(query_size_type), query_size_id,
+ {{SPV_OPERAND_TYPE_ID, {image->result_id()}}});
+
+ auto* component_1 = constant_mgr->GetConstant(coord_component_type, {1});
+ const uint32_t component_1_id =
+ constant_mgr->GetDefiningInstruction(component_1)->result_id();
+ auto* component_0 = constant_mgr->GetConstant(coord_component_type, {0});
+ const uint32_t component_0_id =
+ constant_mgr->GetDefiningInstruction(component_0)->result_id();
+
+ // If the image is a cube array, then the last component of the queried
+ // size is the layer count. In the query, we have to accomodate folding
+ // in the face index ranging from 0 through 5. The inclusive upper bound
+ // on the third coordinate therefore is multiplied by 6.
+ auto* query_size_including_faces = query_size;
+ if (arrayed && (dim == SpvDimCube)) {
+ // Multiply the last coordinate by 6.
+ auto* component_6 = constant_mgr->GetConstant(coord_component_type, {6});
+ const uint32_t component_6_id =
+ constant_mgr->GetDefiningInstruction(component_6)->result_id();
+ assert(query_num_components == 3);
+ auto* multiplicand = constant_mgr->GetConstant(
+ query_size_type, {component_1_id, component_1_id, component_6_id});
+ auto* multiplicand_inst =
+ constant_mgr->GetDefiningInstruction(multiplicand);
+ const auto query_size_including_faces_id = TakeNextId();
+ query_size_including_faces = InsertInst(
+ image_texel_pointer, SpvOpIMul,
+ type_mgr->GetTypeInstruction(query_size_type),
+ query_size_including_faces_id,
+ {{SPV_OPERAND_TYPE_ID, {query_size_including_faces->result_id()}},
+ {SPV_OPERAND_TYPE_ID, {multiplicand_inst->result_id()}}});
+ }
+
+ // Make a coordinate-type with all 1 components.
+ auto* coordinate_1 =
+ query_num_components == 1
+ ? component_1
+ : constant_mgr->GetConstant(
+ query_size_type,
+ std::vector<uint32_t>(query_num_components, component_1_id));
+ // Make a coordinate-type with all 1 components.
+ auto* coordinate_0 =
+ query_num_components == 0
+ ? component_0
+ : constant_mgr->GetConstant(
+ query_size_type,
+ std::vector<uint32_t>(query_num_components, component_0_id));
+
+ const uint32_t query_max_including_faces_id = TakeNextId();
+ auto* query_max_including_faces = InsertInst(
+ image_texel_pointer, SpvOpISub,
+ type_mgr->GetTypeInstruction(query_size_type),
+ query_max_including_faces_id,
+ {{SPV_OPERAND_TYPE_ID, {query_size_including_faces->result_id()}},
+ {SPV_OPERAND_TYPE_ID,
+ {constant_mgr->GetDefiningInstruction(coordinate_1)->result_id()}}});
+
+ // Clamp the coordinate
+ auto* clamp_coord =
+ MakeClampInst(coord, constant_mgr->GetDefiningInstruction(coordinate_0),
+ query_max_including_faces, image_texel_pointer);
+ image_texel_pointer->SetInOperand(1, {clamp_coord->result_id()});
+
+ // Clamp the sample index
+ if (multisampled) {
+ // Get the sample count via OpImageQuerySamples
+ const auto query_samples_id = TakeNextId();
+ auto* query_samples = InsertInst(
+ image_texel_pointer, SpvOpImageQuerySamples,
+ constant_mgr->GetDefiningInstruction(component_0)->type_id(),
+ query_samples_id, {{SPV_OPERAND_TYPE_ID, {image->result_id()}}});
+
+ const auto max_samples_id = TakeNextId();
+ auto* max_samples = InsertInst(image_texel_pointer, SpvOpImageQuerySamples,
+ query_samples->type_id(), max_samples_id,
+ {{SPV_OPERAND_TYPE_ID, {query_samples_id}},
+ {SPV_OPERAND_TYPE_ID, {component_1_id}}});
+
+ auto* clamp_samples = MakeClampInst(
+ samples, constant_mgr->GetDefiningInstruction(coordinate_0),
+ max_samples, image_texel_pointer);
+ image_texel_pointer->SetInOperand(2, {clamp_samples->result_id()});
+
+ } else {
+ // Just replace it with 0. Don't even check what was there before.
+ image_texel_pointer->SetInOperand(2, {component_0_id});
+ }
+
+ def_use_mgr->AnalyzeInstUse(image_texel_pointer);
+
+ return SPV_SUCCESS;
+}
+
+opt::Instruction* GraphicsRobustAccessPass::InsertInst(
+ opt::Instruction* where_inst, SpvOp opcode, uint32_t type_id,
+ uint32_t result_id, const Instruction::OperandList& operands) {
+ module_status_.modified = true;
+ auto* result = where_inst->InsertBefore(
+ MakeUnique<Instruction>(context(), opcode, type_id, result_id, operands));
+ context()->get_def_use_mgr()->AnalyzeInstDefUse(result);
+ auto* basic_block = context()->get_instr_block(where_inst);
+ context()->set_instr_block(result, basic_block);
+ return result;
+}
+
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/graphics_robust_access_pass.h b/source/opt/graphics_robust_access_pass.h
new file mode 100644
index 0000000..f9d17ca
--- /dev/null
+++ b/source/opt/graphics_robust_access_pass.h
@@ -0,0 +1,146 @@
+// Copyright (c) 2019 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.
+
+#ifndef SOURCE_OPT_GRAPHICS_ROBUST_ACCESS_PASS_H_
+#define SOURCE_OPT_GRAPHICS_ROBUST_ACCESS_PASS_H_
+
+#include <map>
+#include <unordered_map>
+
+#include "source/diagnostic.h"
+
+#include "constants.h"
+#include "def_use_manager.h"
+#include "instruction.h"
+#include "module.h"
+#include "pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class GraphicsRobustAccessPass : public Pass {
+ public:
+ GraphicsRobustAccessPass();
+ const char* name() const override { return "graphics-robust-access"; }
+ Status Process() override;
+
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes |
+ IRContext::kAnalysisIdToFuncMapping;
+ };
+
+ private:
+ // Records failure for the current module, and returns a stream
+ // that can be used to provide user error information to the message
+ // consumer.
+ spvtools::DiagnosticStream Fail();
+
+ // Returns SPV_SUCCESS if this pass can correctly process the module.
+ // Otherwise logs a message and returns a failure code.
+ spv_result_t IsCompatibleModule();
+
+ // Transform the current module, if possible. Failure and modification
+ // status is recorded in the |_| member. On failure, error information is
+ // posted to the message consumer. The return value has no significance.
+ spv_result_t ProcessCurrentModule();
+
+ // Process the given function. Updates the state value |_|. Returns true
+ // if the module was modified.
+ bool ProcessAFunction(opt::Function*);
+
+ // Clamps indices in the OpAccessChain or OpInBoundsAccessChain instruction
+ // |access_chain|. Inserts instructions before the given instruction. Updates
+ // analyses and records that the module is modified.
+ void ClampIndicesForAccessChain(Instruction* access_chain);
+
+ // Returns the id of the instruction importing the "GLSL.std.450" extended
+ // instruction set. If it does not yet exist, the import instruction is
+ // created and inserted into the module, and updates |_.modified| and
+ // |_.glsl_insts_id|.
+ uint32_t GetGlslInsts();
+
+ // Returns an instruction which is constant with the given value of the given
+ // type. Ignores any value bits beyond the width of the type.
+ Instruction* GetValueForType(uint64_t value, const analysis::Integer* type);
+
+ // Returns the unsigned value for the given constant. Assumes it's at most
+ // 64 bits wide.
+ uint64_t GetUnsignedValueForConstant(const analysis::Constant* c);
+
+ // Converts an integer value to an unsigned wider integer type, using either
+ // sign extension or zero extension. The new instruction is inserted
+ // immediately before |before_inst|, and is analyzed for definitions and uses.
+ // Returns the newly inserted instruction. Assumes the |value| is an integer
+ // scalar of a narrower type than |bitwidth| bits.
+ Instruction* WidenInteger(bool sign_extend, uint32_t bitwidth,
+ Instruction* value, Instruction* before_inst);
+
+ // Returns a new instruction that invokes the UClamp GLSL.std.450 extended
+ // instruction with the three given operands. That is, the result of the
+ // instruction is:
+ // - |min| if |x| is unsigned-less than |min|
+ // - |max| if |x| is unsigned-more than |max|
+ // - |x| otherwise.
+ // We assume that |min| is unsigned-less-or-equal to |max|, and that the
+ // operands all have the same scalar integer type. The instruction is
+ // inserted before |where|.
+ opt::Instruction* MakeClampInst(Instruction* x, Instruction* min,
+ Instruction* max, Instruction* where);
+
+ // Returns a new instruction which evaluates to the length the runtime array
+ // referenced by the access chain at the specfied index. The instruction is
+ // inserted before the access chain instruction. Returns a null pointer in
+ // some cases if assumptions are violated (rather than asserting out).
+ opt::Instruction* MakeRuntimeArrayLengthInst(Instruction* access_chain,
+ uint32_t operand_index);
+
+ // Clamps the coordinate for an OpImageTexelPointer so it stays within
+ // the bounds of the size of the image. Updates analyses and records that
+ // the module is modified. Returns a status code to indicate success
+ // or failure. If assumptions are not met, returns an error status code
+ // and emits a diagnostic.
+ spv_result_t ClampCoordinateForImageTexelPointer(opt::Instruction* itp);
+
+ // Gets the instruction that defines the given id.
+ opt::Instruction* GetDef(uint32_t id) {
+ return context()->get_def_use_mgr()->GetDef(id);
+ }
+
+ // Returns a new instruction inserted before |where_inst|, and created from
+ // the remaining arguments. Registers the definitions and uses of the new
+ // instruction and also records its block.
+ opt::Instruction* InsertInst(opt::Instruction* where_inst, SpvOp opcode,
+ uint32_t type_id, uint32_t result_id,
+ const Instruction::OperandList& operands);
+
+ // State required for the current module.
+ struct PerModuleState {
+ // This pass modified the module.
+ bool modified = false;
+ // True if there is an error processing the current module, e.g. if
+ // preconditions are not met.
+ bool failed = false;
+ // The id of the GLSL.std.450 extended instruction set. Zero if it does
+ // not exist.
+ uint32_t glsl_insts_id = 0;
+ } module_status_;
+};
+
+} // namespace opt
+} // namespace spvtools
+
+#endif // SOURCE_OPT_GRAPHICS_ROBUST_ACCESS_PASS_H_
diff --git a/source/opt/instruction.h b/source/opt/instruction.h
index d507d6c..d1c4ce1 100644
--- a/source/opt/instruction.h
+++ b/source/opt/instruction.h
@@ -399,7 +399,7 @@
inline bool operator<(const Instruction&) const;
// Takes ownership of the instruction owned by |i| and inserts it immediately
- // before |this|. Returns the insterted instruction.
+ // before |this|. Returns the inserted instruction.
Instruction* InsertBefore(std::unique_ptr<Instruction>&& i);
// Takes ownership of the instructions in |list| and inserts them in order
// immediately before |this|. Returns the first inserted instruction.
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 5c1e6ca..2dd1708 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -22,6 +22,7 @@
#include <source/spirv_optimizer_options.h>
#include "source/opt/build_module.h"
+#include "source/opt/graphics_robust_access_pass.h"
#include "source/opt/log.h"
#include "source/opt/pass_manager.h"
#include "source/opt/passes.h"
@@ -472,6 +473,8 @@
RegisterPass(CreateLegalizeVectorShufflePass());
} else if (pass_name == "decompose-initialized-variables") {
RegisterPass(CreateDecomposeInitializedVariablesPass());
+ } else if (pass_name == "graphics-robust-access") {
+ RegisterPass(CreateGraphicsRobustAccessPass());
} else {
Errorf(consumer(), nullptr, {},
"Unknown flag '--%s'. Use --help for a list of valid flags",
@@ -878,4 +881,9 @@
MakeUnique<opt::SplitInvalidUnreachablePass>());
}
+Optimizer::PassToken CreateGraphicsRobustAccessPass() {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::GraphicsRobustAccessPass>());
+}
+
} // namespace spvtools
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 0a348e4..5eddc22 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -37,6 +37,7 @@
#include "source/opt/fold_spec_constant_op_and_composite_pass.h"
#include "source/opt/freeze_spec_constant_value_pass.h"
#include "source/opt/generate_webgpu_initializers_pass.h"
+#include "source/opt/graphics_robust_access_pass.h"
#include "source/opt/if_conversion.h"
#include "source/opt/inline_exhaustive_pass.h"
#include "source/opt/inline_opaque_pass.h"
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index 246c116..6131c9b 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -25,6 +25,7 @@
code_sink_test.cpp
combine_access_chains_test.cpp
compact_ids_test.cpp
+ constants_test.cpp
constant_manager_test.cpp
copy_prop_array_test.cpp
dead_branch_elim_test.cpp
@@ -44,6 +45,7 @@
freeze_spec_const_test.cpp
function_test.cpp
generate_webgpu_initializers_test.cpp
+ graphics_robust_access_test.cpp
if_conversion_test.cpp
inline_opaque_test.cpp
inline_test.cpp
diff --git a/test/opt/constants_test.cpp b/test/opt/constants_test.cpp
new file mode 100644
index 0000000..55c92a5
--- /dev/null
+++ b/test/opt/constants_test.cpp
@@ -0,0 +1,167 @@
+// Copyright (c) 2019 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/opt/constants.h"
+
+#include <gtest/gtest-param-test.h>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "source/opt/types.h"
+
+namespace spvtools {
+namespace opt {
+namespace analysis {
+namespace {
+
+using ConstantTest = ::testing::Test;
+using ::testing::ValuesIn;
+
+template <typename T>
+struct GetExtendedValueCase {
+ bool is_signed;
+ int width;
+ std::vector<uint32_t> words;
+ T expected_value;
+};
+
+using GetSignExtendedValueCase = GetExtendedValueCase<int64_t>;
+using GetZeroExtendedValueCase = GetExtendedValueCase<uint64_t>;
+
+using GetSignExtendedValueTest =
+ ::testing::TestWithParam<GetSignExtendedValueCase>;
+using GetZeroExtendedValueTest =
+ ::testing::TestWithParam<GetZeroExtendedValueCase>;
+
+TEST_P(GetSignExtendedValueTest, Case) {
+ Integer type(GetParam().width, GetParam().is_signed);
+ IntConstant value(&type, GetParam().words);
+
+ EXPECT_EQ(GetParam().expected_value, value.GetSignExtendedValue());
+}
+
+TEST_P(GetZeroExtendedValueTest, Case) {
+ Integer type(GetParam().width, GetParam().is_signed);
+ IntConstant value(&type, GetParam().words);
+
+ EXPECT_EQ(GetParam().expected_value, value.GetZeroExtendedValue());
+}
+
+const uint32_t k32ones = ~uint32_t(0);
+const uint64_t k64ones = ~uint64_t(0);
+const int64_t kSBillion = 1000 * 1000 * 1000;
+const uint64_t kUBillion = 1000 * 1000 * 1000;
+
+INSTANTIATE_TEST_SUITE_P(AtMost32Bits, GetSignExtendedValueTest,
+ ValuesIn(std::vector<GetSignExtendedValueCase>{
+ // 4 bits
+ {false, 4, {0}, 0},
+ {false, 4, {7}, 7},
+ {false, 4, {15}, 15},
+ {true, 4, {0}, 0},
+ {true, 4, {7}, 7},
+ {true, 4, {0xfffffff8}, -8},
+ {true, 4, {k32ones}, -1},
+ // 16 bits
+ {false, 16, {0}, 0},
+ {false, 16, {32767}, 32767},
+ {false, 16, {32768}, 32768},
+ {false, 16, {65000}, 65000},
+ {true, 16, {0}, 0},
+ {true, 16, {32767}, 32767},
+ {true, 16, {0xfffffff8}, -8},
+ {true, 16, {k32ones}, -1},
+ // 32 bits
+ {false, 32, {0}, 0},
+ {false, 32, {1000000}, 1000000},
+ {true, 32, {0xfffffff8}, -8},
+ {true, 32, {k32ones}, -1},
+ }));
+
+INSTANTIATE_TEST_SUITE_P(AtMost64Bits, GetSignExtendedValueTest,
+ ValuesIn(std::vector<GetSignExtendedValueCase>{
+ // 48 bits
+ {false, 48, {0, 0}, 0},
+ {false, 48, {5, 0}, 5},
+ {false, 48, {0xfffffff8, k32ones}, -8},
+ {false, 48, {k32ones, k32ones}, -1},
+ {false, 48, {0xdcd65000, 1}, 8 * kSBillion},
+ {true, 48, {0xfffffff8, k32ones}, -8},
+ {true, 48, {k32ones, k32ones}, -1},
+ {true, 48, {0xdcd65000, 1}, 8 * kSBillion},
+
+ // 64 bits
+ {false, 64, {12, 0}, 12},
+ {false, 64, {0xdcd65000, 1}, 8 * kSBillion},
+ {false, 48, {0xfffffff8, k32ones}, -8},
+ {false, 64, {k32ones, k32ones}, -1},
+ {true, 64, {12, 0}, 12},
+ {true, 64, {0xdcd65000, 1}, 8 * kSBillion},
+ {true, 48, {0xfffffff8, k32ones}, -8},
+ {true, 64, {k32ones, k32ones}, -1},
+ }));
+
+INSTANTIATE_TEST_SUITE_P(AtMost32Bits, GetZeroExtendedValueTest,
+ ValuesIn(std::vector<GetZeroExtendedValueCase>{
+ // 4 bits
+ {false, 4, {0}, 0},
+ {false, 4, {7}, 7},
+ {false, 4, {15}, 15},
+ {true, 4, {0}, 0},
+ {true, 4, {7}, 7},
+ {true, 4, {0xfffffff8}, 0xfffffff8},
+ {true, 4, {k32ones}, k32ones},
+ // 16 bits
+ {false, 16, {0}, 0},
+ {false, 16, {32767}, 32767},
+ {false, 16, {32768}, 32768},
+ {false, 16, {65000}, 65000},
+ {true, 16, {0}, 0},
+ {true, 16, {32767}, 32767},
+ {true, 16, {0xfffffff8}, 0xfffffff8},
+ {true, 16, {k32ones}, k32ones},
+ // 32 bits
+ {false, 32, {0}, 0},
+ {false, 32, {1000000}, 1000000},
+ {true, 32, {0xfffffff8}, 0xfffffff8},
+ {true, 32, {k32ones}, k32ones},
+ }));
+
+INSTANTIATE_TEST_SUITE_P(AtMost64Bits, GetZeroExtendedValueTest,
+ ValuesIn(std::vector<GetZeroExtendedValueCase>{
+ // 48 bits
+ {false, 48, {0, 0}, 0},
+ {false, 48, {5, 0}, 5},
+ {false, 48, {0xfffffff8, k32ones}, uint64_t(-8)},
+ {false, 48, {k32ones, k32ones}, uint64_t(-1)},
+ {false, 48, {0xdcd65000, 1}, 8 * kUBillion},
+ {true, 48, {0xfffffff8, k32ones}, uint64_t(-8)},
+ {true, 48, {k32ones, k32ones}, uint64_t(-1)},
+ {true, 48, {0xdcd65000, 1}, 8 * kUBillion},
+
+ // 64 bits
+ {false, 64, {12, 0}, 12},
+ {false, 64, {0xdcd65000, 1}, 8 * kUBillion},
+ {false, 48, {0xfffffff8, k32ones}, uint64_t(-8)},
+ {false, 64, {k32ones, k32ones}, k64ones},
+ {true, 64, {12, 0}, 12},
+ {true, 64, {0xdcd65000, 1}, 8 * kUBillion},
+ {true, 48, {0xfffffff8, k32ones}, uint64_t(-8)},
+ {true, 64, {k32ones, k32ones}, k64ones},
+ }));
+
+} // namespace
+} // namespace analysis
+} // namespace opt
+} // namespace spvtools
diff --git a/test/opt/graphics_robust_access_test.cpp b/test/opt/graphics_robust_access_test.cpp
new file mode 100644
index 0000000..137d0e8
--- /dev/null
+++ b/test/opt/graphics_robust_access_test.cpp
@@ -0,0 +1,1307 @@
+// Copyright (c) 2019 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 <array>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+#include "source/opt/graphics_robust_access_pass.h"
+
+namespace {
+
+using namespace spvtools;
+
+using opt::GraphicsRobustAccessPass;
+using GraphicsRobustAccessTest = opt::PassTest<::testing::Test>;
+
+// Test incompatible module, determined at module-level.
+
+TEST_F(GraphicsRobustAccessTest, FailNotShader) {
+ const std::string text = R"(
+; CHECK: Can only process Shader modules
+OpCapability Kernel
+)";
+
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
+}
+
+TEST_F(GraphicsRobustAccessTest, FailCantProcessVariablePointers) {
+ const std::string text = R"(
+; CHECK: Can't process modules with VariablePointers capability
+OpCapability VariablePointers
+)";
+
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
+}
+
+TEST_F(GraphicsRobustAccessTest, FailCantProcessVariablePointersStorageBuffer) {
+ const std::string text = R"(
+; CHECK: Can't process modules with VariablePointersStorageBuffer capability
+OpCapability VariablePointersStorageBuffer
+)";
+
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
+}
+
+TEST_F(GraphicsRobustAccessTest, FailCantProcessRuntimeDescriptorArrayEXT) {
+ const std::string text = R"(
+; CHECK: Can't process modules with RuntimeDescriptorArrayEXT capability
+OpCapability RuntimeDescriptorArrayEXT
+)";
+
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
+}
+
+TEST_F(GraphicsRobustAccessTest, FailCantProcessPhysical32AddressingModel) {
+ const std::string text = R"(
+; CHECK: Addressing model must be Logical. Found OpMemoryModel Physical32 OpenCL
+OpCapability Shader
+OpMemoryModel Physical32 OpenCL
+)";
+
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
+}
+
+TEST_F(GraphicsRobustAccessTest, FailCantProcessPhysical64AddressingModel) {
+ const std::string text = R"(
+; CHECK: Addressing model must be Logical. Found OpMemoryModel Physical64 OpenCL
+OpCapability Shader
+OpMemoryModel Physical64 OpenCL
+)";
+
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
+}
+
+TEST_F(GraphicsRobustAccessTest,
+ FailCantProcessPhysicalStorageBuffer64EXTAddressingModel) {
+ const std::string text = R"(
+; CHECK: Addressing model must be Logical. Found OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+OpCapability Shader
+OpMemoryModel PhysicalStorageBuffer64EXT GLSL450
+)";
+
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(text);
+}
+
+// Test access chains
+
+// Returns the names of access chain instructions handled by the pass.
+// For the purposes of this pass, regular and in-bounds access chains are the
+// same.)
+std::vector<const char*> AccessChains() {
+ return {"OpAccessChain", "OpInBoundsAccessChain"};
+}
+
+std::string ShaderPreamble() {
+ return R"(
+ OpCapability Shader
+ OpMemoryModel Logical Simple
+ OpEntryPoint GLCompute %main "main"
+)";
+}
+
+std::string ShaderPreamble(const std::vector<std::string>& names) {
+ std::ostringstream os;
+ os << ShaderPreamble();
+ for (auto& name : names) {
+ os << " OpName %" << name << " \"" << name << "\"\n";
+ }
+ return os.str();
+}
+
+std::string ShaderPreambleAC() {
+ return ShaderPreamble({"ac", "ptr_ty", "var"});
+}
+
+std::string ShaderPreambleAC(const std::vector<std::string>& names) {
+ auto names2 = names;
+ names2.push_back("ac");
+ names2.push_back("ptr_ty");
+ names2.push_back("var");
+ return ShaderPreamble(names2);
+}
+
+std::string DecoSSBO() {
+ return R"(
+ OpDecorate %ssbo_s BufferBlock
+ OpMemberDecorate %ssbo_s 0 Offset 0
+ OpMemberDecorate %ssbo_s 1 Offset 4
+ OpMemberDecorate %ssbo_s 2 Offset 16
+ OpDecorate %var DescriptorSet 0
+ OpDecorate %var Binding 0
+)";
+}
+
+std::string TypesVoid() {
+ return R"(
+ %void = OpTypeVoid
+ %void_fn = OpTypeFunction %void
+)";
+}
+
+std::string TypesInt() {
+ return R"(
+ %uint = OpTypeInt 32 0
+ %int = OpTypeInt 32 1
+)";
+}
+
+std::string TypesFloat() {
+ return R"(
+ %float = OpTypeFloat 32
+)";
+}
+
+std::string TypesShort() {
+ return R"(
+ %ushort = OpTypeInt 16 0
+ %short = OpTypeInt 16 1
+)";
+}
+
+std::string TypesLong() {
+ return R"(
+ %ulong = OpTypeInt 64 0
+ %long = OpTypeInt 64 1
+)";
+}
+
+std::string MainPrefix() {
+ return R"(
+ %main = OpFunction %void None %void_fn
+ %entry = OpLabel
+)";
+}
+
+std::string MainSuffix() {
+ return R"(
+ OpReturn
+ OpFunctionEnd
+)";
+}
+
+std::string ACCheck(const std::string& access_chain_inst,
+ const std::string& original,
+ const std::string& transformed) {
+ return "\n ; CHECK: %ac = " + access_chain_inst + " %ptr_ty %var" +
+ (transformed.size() ? " " : "") + transformed +
+ "\n ; CHECK-NOT: " + access_chain_inst +
+ "\n ; CHECK-NEXT: OpReturn"
+ "\n %ac = " +
+ access_chain_inst + " %ptr_ty %var " + (original.size() ? " " : "") +
+ original + "\n";
+}
+
+std::string ACCheckFail(const std::string& access_chain_inst,
+ const std::string& original,
+ const std::string& transformed) {
+ return "\n ; CHECK: %ac = " + access_chain_inst + " %ptr_ty %var" +
+ (transformed.size() ? " " : "") + transformed +
+ "\n ; CHECK-NOT: " + access_chain_inst +
+ "\n ; CHECK-NOT: OpReturn"
+ "\n %ac = " +
+ access_chain_inst + " %ptr_ty %var " + (original.size() ? " " : "") +
+ original + "\n";
+}
+
+// Access chain into:
+// Vector
+// Vector sizes 2, 3, 4
+// Matrix
+// Matrix columns 2, 4
+// Component is vector 2, 4
+// Array
+// Struct
+// TODO(dneto): RuntimeArray
+
+TEST_F(GraphicsRobustAccessTest, ACVectorLeastInboundConstantUntouched) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
+ %uvec2 = OpTypeVector %uint 2
+ %var_ty = OpTypePointer Function %uvec2
+ %ptr_ty = OpTypePointer Function %uint
+ %uint_0 = OpConstant %uint 0
+ )"
+ << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_0", "%uint_0")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACVectorMostInboundConstantUntouched) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
+ %v4uint = OpTypeVector %uint 4
+ %var_ty = OpTypePointer Function %v4uint
+ %ptr_ty = OpTypePointer Function %uint
+ %uint_3 = OpConstant %uint 3
+ )"
+ << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_3", "%uint_3")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACVectorExcessConstantClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
+ %v4uint = OpTypeVector %uint 4
+ %var_ty = OpTypePointer Function %v4uint
+ %ptr_ty = OpTypePointer Function %uint
+ %uint_4 = OpConstant %uint 4
+ )"
+ << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%uint_4", "%uint_3")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACVectorNegativeConstantClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
+ %v4uint = OpTypeVector %uint 4
+ %var_ty = OpTypePointer Function %v4uint
+ %ptr_ty = OpTypePointer Function %uint
+ %int_n1 = OpConstant %int -1
+ )"
+ << MainPrefix() << R"(
+ ; CHECK: %int_0 = OpConstant %int 0
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%int_n1", "%int_0")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+// Like the previous test, but ensures the pass knows how to modify an index
+// which does not come first in the access chain.
+TEST_F(GraphicsRobustAccessTest, ACVectorInArrayNegativeConstantClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt() << R"(
+ %v4uint = OpTypeVector %uint 4
+ %uint_1 = OpConstant %uint 1
+ %uint_2 = OpConstant %uint 2
+ %arr = OpTypeArray %v4uint %uint_2
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %uint
+ %int_n1 = OpConstant %int -1
+ )"
+ << MainPrefix() << R"(
+ ; CHECK: %int_0 = OpConstant %int 0
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%uint_1 %int_n1", "%uint_1 %int_0") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACVectorGeneralClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt() << R"(
+ %v4uint = OpTypeVector %uint 4
+ %var_ty = OpTypePointer Function %v4uint
+ %ptr_ty = OpTypePointer Function %uint
+ %i = OpUndef %int)"
+ << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK-DAG: %int_3 = OpConstant %int 3
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_3
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACVectorGeneralShortClamped) {
+ // Show that signed 16 bit integers are clamped as well.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int16\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesShort() <<
+ R"(
+ %v4short = OpTypeVector %short 4
+ %var_ty = OpTypePointer Function %v4short
+ %ptr_ty = OpTypePointer Function %short
+ %i = OpUndef %short)"
+ << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-NOT: = OpTypeInt 32
+ ; CHECK-DAG: %short_0 = OpConstant %short 0
+ ; CHECK-DAG: %short_3 = OpConstant %short 3
+ ; CHECK-NOT: = OpTypeInt 32
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %short %[[GLSLSTD450]] UClamp %i %short_0 %short_3
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACVectorGeneralUShortClamped) {
+ // Show that unsigned 16 bit integers are clamped as well.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int16\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesShort() <<
+ R"(
+ %v4ushort = OpTypeVector %ushort 4
+ %var_ty = OpTypePointer Function %v4ushort
+ %ptr_ty = OpTypePointer Function %ushort
+ %i = OpUndef %ushort)"
+ << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-NOT: = OpTypeInt 32
+ ; CHECK-DAG: %ushort_0 = OpConstant %ushort 0
+ ; CHECK-DAG: %ushort_3 = OpConstant %ushort 3
+ ; CHECK-NOT: = OpTypeInt 32
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %ushort %[[GLSLSTD450]] UClamp %i %ushort_0 %ushort_3
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACVectorGeneralLongClamped) {
+ // Show that signed 64 bit integers are clamped as well.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int64\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesLong() <<
+ R"(
+ %v4long = OpTypeVector %long 4
+ %var_ty = OpTypePointer Function %v4long
+ %ptr_ty = OpTypePointer Function %long
+ %i = OpUndef %long)"
+ << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-NOT: = OpTypeInt 32
+ ; CHECK-DAG: %long_0 = OpConstant %long 0
+ ; CHECK-DAG: %long_3 = OpConstant %long 3
+ ; CHECK-NOT: = OpTypeInt 32
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] UClamp %i %long_0 %long_3
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACVectorGeneralULongClamped) {
+ // Show that unsigned 64 bit integers are clamped as well.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int64\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesLong() <<
+ R"(
+ %v4ulong = OpTypeVector %ulong 4
+ %var_ty = OpTypePointer Function %v4ulong
+ %ptr_ty = OpTypePointer Function %ulong
+ %i = OpUndef %ulong)"
+ << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-NOT: = OpTypeInt 32
+ ; CHECK-DAG: %ulong_0 = OpConstant %ulong 0
+ ; CHECK-DAG: %ulong_3 = OpConstant %ulong 3
+ ; CHECK-NOT: = OpTypeInt 32
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] UClamp %i %ulong_0 %ulong_3
+ %var = OpVariable %var_ty Function)" << ACCheck(ac, "%i", "%[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACMatrixLeastInboundConstantUntouched) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %v2float = OpTypeVector %float 2
+ %mat4v2float = OpTypeMatrix %v2float 4
+ %var_ty = OpTypePointer Function %mat4v2float
+ %ptr_ty = OpTypePointer Function %float
+ %uint_0 = OpConstant %uint 0
+ %uint_1 = OpConstant %uint 1
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%uint_0 %uint_1", "%uint_0 %uint_1")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACMatrixMostInboundConstantUntouched) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %v2float = OpTypeVector %float 2
+ %mat4v2float = OpTypeMatrix %v2float 4
+ %var_ty = OpTypePointer Function %mat4v2float
+ %ptr_ty = OpTypePointer Function %float
+ %uint_1 = OpConstant %uint 1
+ %uint_3 = OpConstant %uint 3
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%uint_3 %uint_1", "%uint_3 %uint_1")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACMatrixExcessConstantClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %v2float = OpTypeVector %float 2
+ %mat4v2float = OpTypeMatrix %v2float 4
+ %var_ty = OpTypePointer Function %mat4v2float
+ %ptr_ty = OpTypePointer Function %float
+ %uint_1 = OpConstant %uint 1
+ %uint_4 = OpConstant %uint 4
+ )" << MainPrefix() << R"(
+ ; CHECK: %uint_3 = OpConstant %uint 3
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%uint_4 %uint_1", "%uint_3 %uint_1")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACMatrixNegativeConstantClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %v2float = OpTypeVector %float 2
+ %mat4v2float = OpTypeMatrix %v2float 4
+ %var_ty = OpTypePointer Function %mat4v2float
+ %ptr_ty = OpTypePointer Function %float
+ %uint_1 = OpConstant %uint 1
+ %int_n1 = OpConstant %int -1
+ )" << MainPrefix() << R"(
+ ; CHECK: %int_0 = OpConstant %int 0
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%int_n1 %uint_1", "%int_0 %uint_1") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACMatrixGeneralClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %v2float = OpTypeVector %float 2
+ %mat4v2float = OpTypeMatrix %v2float 4
+ %var_ty = OpTypePointer Function %mat4v2float
+ %ptr_ty = OpTypePointer Function %float
+ %uint_1 = OpConstant %uint 1
+ %i = OpUndef %int
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK-DAG: %int_3 = OpConstant %int 3
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_3
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%i %uint_1", "%[[clamp]] %uint_1") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayLeastInboundConstantUntouched) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %uint_200 = OpConstant %uint 200
+ %arr = OpTypeArray %float %uint_200
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %int_0 = OpConstant %int 0
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%int_0", "%int_0") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayMostInboundConstantUntouched) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %uint_200 = OpConstant %uint 200
+ %arr = OpTypeArray %float %uint_200
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %int_199 = OpConstant %int 199
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%int_199", "%int_199") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayGeneralClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %uint_200 = OpConstant %uint 200
+ %arr = OpTypeArray %float %uint_200
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpUndef %int
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK-DAG: %int_199 = OpConstant %int 199
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_199
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayGeneralShortIndexUIntBoundsClamped) {
+ // Index is signed short, array bounds overflows the index type.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int16\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
+ << TypesShort() << TypesFloat() << R"(
+ %uint_70000 = OpConstant %uint 70000 ; overflows 16bits
+ %arr = OpTypeArray %float %uint_70000
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpUndef %short
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %uint_0 = OpConstant %uint 0
+ ; CHECK-DAG: %uint_69999 = OpConstant %uint 69999
+ ; CHECK: OpLabel
+ ; CHECK: %[[i_ext:\w+]] = OpSConvert %uint %i
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %[[i_ext]] %uint_0 %uint_69999
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayGeneralUShortIndexIntBoundsClamped) {
+ // Index is unsigned short, array bounds overflows the index type.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int16\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
+ << TypesShort() << TypesFloat() << R"(
+ %int_70000 = OpConstant %int 70000 ; overflows 16bits
+ %arr = OpTypeArray %float %int_70000
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpUndef %ushort
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %uint_0 = OpConstant %uint 0
+ ; CHECK-DAG: %uint_69999 = OpConstant %uint 69999
+ ; CHECK: OpLabel
+ ; CHECK: %[[i_ext:\w+]] = OpUConvert %uint %i
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %[[i_ext]] %uint_0 %uint_69999
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayGeneralUIntIndexShortBoundsClamped) {
+ // Signed int index i is wider than the array bounds type.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int16\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
+ << TypesShort() << TypesFloat() << R"(
+ %short_200 = OpConstant %short 200
+ %arr = OpTypeArray %float %short_200
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpUndef %uint
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %uint_0 = OpConstant %uint 0
+ ; CHECK-DAG: %uint_199 = OpConstant %uint 199
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %i %uint_0 %uint_199
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayGeneralIntIndexUShortBoundsClamped) {
+ // Unsigned int index i is wider than the array bounds type.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int16\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
+ << TypesShort() << TypesFloat() << R"(
+ %ushort_200 = OpConstant %ushort 200
+ %arr = OpTypeArray %float %ushort_200
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpUndef %int
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK-DAG: %int_199 = OpConstant %int 199
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_199
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayGeneralLongIndexUIntBoundsClamped) {
+ // Signed long index i is wider than the array bounds type.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int64\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
+ << TypesLong() << TypesFloat() << R"(
+ %uint_200 = OpConstant %uint 200
+ %arr = OpTypeArray %float %uint_200
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpUndef %long
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %long_0 = OpConstant %long 0
+ ; CHECK-DAG: %long_199 = OpConstant %long 199
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] UClamp %i %long_0 %long_199
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayGeneralULongIndexIntBoundsClamped) {
+ // Unsigned long index i is wider than the array bounds type.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int64\n"
+ << ShaderPreambleAC({"i"}) << TypesVoid() << TypesInt()
+ << TypesLong() << TypesFloat() << R"(
+ %int_200 = OpConstant %int 200
+ %arr = OpTypeArray %float %int_200
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpUndef %ulong
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %ulong_0 = OpConstant %ulong 0
+ ; CHECK-DAG: %ulong_199 = OpConstant %ulong 199
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] UClamp %i %ulong_0 %ulong_199
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%i", "%[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArraySpecIdSizedAlwaysClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"spec200"}) << R"(
+ OpDecorate %spec200 SpecId 0 )" << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %spec200 = OpSpecConstant %int 200
+ %arr = OpTypeArray %float %spec200
+ %var_ty = OpTypePointer Function %arr
+ %ptr_ty = OpTypePointer Function %float
+ %uint_5 = OpConstant %uint 5
+ )" << MainPrefix() << R"(
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %uint_0 = OpConstant %uint 0
+ ; CHECK-DAG: %uint_1 = OpConstant %uint 1
+ ; CHECK: OpLabel
+ ; CHECK: %[[max:\w+]] = OpISub %uint %spec200 %uint_1
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %uint_5 %uint_0 %[[max]]
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%uint_5", "%[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACStructLeastUntouched) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %struct = OpTypeStruct %float %float %float
+ %var_ty = OpTypePointer Function %struct
+ %ptr_ty = OpTypePointer Function %float
+ %int_0 = OpConstant %int 0
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%int_0", "%int_0") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACStructMostUntouched) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %struct = OpTypeStruct %float %float %float
+ %var_ty = OpTypePointer Function %struct
+ %ptr_ty = OpTypePointer Function %float
+ %int_2 = OpConstant %int 2
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function)"
+ << ACCheck(ac, "%int_2", "%int_2") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACStructSpecConstantFail) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"struct", "spec200"})
+ << "OpDecorate %spec200 SpecId 0\n"
+ <<
+
+ TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %spec200 = OpSpecConstant %int 200
+ %struct = OpTypeStruct %float %float %float
+ %var_ty = OpTypePointer Function %struct
+ %ptr_ty = OpTypePointer Function %float
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function
+ ; CHECK: Member index into struct is not a constant integer
+ ; CHECK-SAME: %spec200 = OpSpecConstant %int 200
+ )"
+ << ACCheckFail(ac, "%spec200", "%spec200") << MainSuffix();
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACStructFloatConstantFail) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"struct"}) <<
+
+ TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %float_2 = OpConstant %float 2
+ %struct = OpTypeStruct %float %float %float
+ %var_ty = OpTypePointer Function %struct
+ %ptr_ty = OpTypePointer Function %float
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function
+ ; CHECK: Member index into struct is not a constant integer
+ ; CHECK-SAME: %float_2 = OpConstant %float 2
+ )"
+ << ACCheckFail(ac, "%float_2", "%float_2") << MainSuffix();
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACStructNonConstantFail) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"struct", "i"}) <<
+
+ TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %float_2 = OpConstant %float 2
+ %struct = OpTypeStruct %float %float %float
+ %var_ty = OpTypePointer Function %struct
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpUndef %int
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function
+ ; CHECK: Member index into struct is not a constant integer
+ ; CHECK-SAME: %i = OpUndef %int
+ )"
+ << ACCheckFail(ac, "%i", "%i") << MainSuffix();
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACStructExcessFail) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"struct", "i"}) << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %struct = OpTypeStruct %float %float %float
+ %var_ty = OpTypePointer Function %struct
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpConstant %int 4
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function
+ ; CHECK: Member index 4 is out of bounds for struct type:
+ ; CHECK-SAME: %struct = OpTypeStruct %float %float %float
+ )"
+ << ACCheckFail(ac, "%i", "%i") << MainSuffix();
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACStructNegativeFail) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"struct", "i"}) << TypesVoid() << TypesInt()
+ << TypesFloat() << R"(
+ %struct = OpTypeStruct %float %float %float
+ %var_ty = OpTypePointer Function %struct
+ %ptr_ty = OpTypePointer Function %float
+ %i = OpConstant %int -1
+ )" << MainPrefix() << R"(
+ %var = OpVariable %var_ty Function
+ ; CHECK: Member index -1 is out of bounds for struct type:
+ ; CHECK-SAME: %struct = OpTypeStruct %float %float %float
+ )"
+ << ACCheckFail(ac, "%i", "%i") << MainSuffix();
+ SinglePassRunAndFail<GraphicsRobustAccessPass>(shaders.str());
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACRTArrayLeastInboundClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC() << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 "
+ << DecoSSBO() << TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %rtarr = OpTypeRuntimeArray %float
+ %ssbo_s = OpTypeStruct %uint %uint %rtarr
+ %var_ty = OpTypePointer Uniform %ssbo_s
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_0 = OpConstant %int 0
+ %int_2 = OpConstant %int 2
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK: %int_1 = OpConstant %int 1
+ ; CHECK: OpLabel
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
+ ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %int_0 %int_0 %[[max]]
+ )"
+ << MainPrefix() << ACCheck(ac, "%int_2 %int_0", "%int_2 %[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralShortIndexClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int16\n"
+ << ShaderPreambleAC({"i"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO()
+ << TypesVoid() << TypesShort() << TypesFloat() << R"(
+ %rtarr = OpTypeRuntimeArray %float
+ %ssbo_s = OpTypeStruct %short %short %rtarr
+ %var_ty = OpTypePointer Uniform %ssbo_s
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %short_2 = OpConstant %short 2
+ %i = OpUndef %short
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK: %uint = OpTypeInt 32 0
+ ; CHECK-DAG: %uint_1 = OpConstant %uint 1
+ ; CHECK-DAG: %uint_0 = OpConstant %uint 0
+ ; CHECK: OpLabel
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
+ ; CHECK-DAG: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1
+ ; CHECK-DAG: %[[i_ext:\w+]] = OpSConvert %uint %i
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %[[i_ext]] %uint_0 %[[max]]
+ )"
+ << MainPrefix() << ACCheck(ac, "%short_2 %i", "%short_2 %[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralUShortIndexClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int16\n"
+ << ShaderPreambleAC({"i"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO()
+ << TypesVoid() << TypesShort() << TypesFloat() << R"(
+ %rtarr = OpTypeRuntimeArray %float
+ %ssbo_s = OpTypeStruct %short %short %rtarr
+ %var_ty = OpTypePointer Uniform %ssbo_s
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %short_2 = OpConstant %short 2
+ %i = OpUndef %ushort
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK: %uint = OpTypeInt 32 0
+ ; CHECK-DAG: %uint_1 = OpConstant %uint 1
+ ; CHECK-DAG: %uint_0 = OpConstant %uint 0
+ ; CHECK: OpLabel
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
+ ; CHECK-DAG: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1
+ ; CHECK-DAG: %[[i_ext:\w+]] = OpSConvert %uint %i
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %[[i_ext]] %uint_0 %[[max]]
+ )"
+ << MainPrefix() << ACCheck(ac, "%short_2 %i", "%short_2 %[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralIntIndexClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO()
+ << TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %rtarr = OpTypeRuntimeArray %float
+ %ssbo_s = OpTypeStruct %int %int %rtarr
+ %var_ty = OpTypePointer Uniform %ssbo_s
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_2 = OpConstant %int 2
+ %i = OpUndef %int
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %int_1 = OpConstant %int 1
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK: OpLabel
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
+ ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %[[max]]
+ )" << MainPrefix()
+ << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralUIntIndexClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO()
+ << TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %rtarr = OpTypeRuntimeArray %float
+ %ssbo_s = OpTypeStruct %int %int %rtarr
+ %var_ty = OpTypePointer Uniform %ssbo_s
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_2 = OpConstant %int 2
+ %i = OpUndef %uint
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %uint_1 = OpConstant %uint 1
+ ; CHECK-DAG: %uint_0 = OpConstant %uint 0
+ ; CHECK: OpLabel
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
+ ; CHECK: %[[max:\w+]] = OpISub %uint %[[arrlen]] %uint_1
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %uint %[[GLSLSTD450]] UClamp %i %uint_0 %[[max]]
+ )" << MainPrefix()
+ << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]") << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralLongIndexClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int64" << ShaderPreambleAC({"i"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO()
+ << TypesVoid() << TypesInt() << TypesLong() << TypesFloat() << R"(
+ %rtarr = OpTypeRuntimeArray %float
+ %ssbo_s = OpTypeStruct %int %int %rtarr
+ %var_ty = OpTypePointer Uniform %ssbo_s
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_2 = OpConstant %int 2
+ %i = OpUndef %long
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %long_0 = OpConstant %long 0
+ ; CHECK-DAG: %long_1 = OpConstant %long 1
+ ; CHECK: OpLabel
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
+ ; CHECK: %[[arrlen_ext:\w+]] = OpUConvert %ulong %[[arrlen]]
+ ; CHECK: %[[max:\w+]] = OpISub %long %[[arrlen_ext]] %long_1
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %long %[[GLSLSTD450]] UClamp %i %long_0 %[[max]]
+ )"
+ << MainPrefix() << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACRTArrayGeneralULongIndexClamped) {
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << "OpCapability Int64" << ShaderPreambleAC({"i"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 4 " << DecoSSBO()
+ << TypesVoid() << TypesInt() << TypesLong() << TypesFloat() << R"(
+ %rtarr = OpTypeRuntimeArray %float
+ %ssbo_s = OpTypeStruct %int %int %rtarr
+ %var_ty = OpTypePointer Uniform %ssbo_s
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_2 = OpConstant %int 2
+ %i = OpUndef %ulong
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %ulong_0 = OpConstant %ulong 0
+ ; CHECK-DAG: %ulong_1 = OpConstant %ulong 1
+ ; CHECK: OpLabel
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
+ ; CHECK: %[[arrlen_ext:\w+]] = OpUConvert %ulong %[[arrlen]]
+ ; CHECK: %[[max:\w+]] = OpISub %ulong %[[arrlen_ext]] %ulong_1
+ ; CHECK: %[[clamp:\w+]] = OpExtInst %ulong %[[GLSLSTD450]] UClamp %i %ulong_0 %[[max]]
+ )"
+ << MainPrefix() << ACCheck(ac, "%int_2 %i", "%int_2 %[[clamp]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACRTArrayStructVectorElem) {
+ // The point of this test is that the access chain can have indices past the
+ // index into the runtime array. For good measure, the index into the final
+ // struct is out of bounds. We have to clamp that index too.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i", "j"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 32\n"
+ << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n"
+ << "OpMemberDecorate %rtelem 1 Offset 16\n"
+ << TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %v4float = OpTypeVector %float 4
+ %rtelem = OpTypeStruct %v4float %v4float
+ %rtarr = OpTypeRuntimeArray %rtelem
+ %ssbo_s = OpTypeStruct %int %int %rtarr
+ %var_ty = OpTypePointer Uniform %ssbo_s
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %i = OpUndef %int
+ %j = OpUndef %int
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK-DAG: %int_3 = OpConstant %int 3
+ ; CHECK: OpLabel
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %var 2
+ ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
+ ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %[[max]]
+ ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %j %int_0 %int_3
+ )" << MainPrefix()
+ << ACCheck(ac, "%int_2 %i %int_1 %j",
+ "%int_2 %[[clamp_i]] %int_1 %[[clamp_j]]")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACArrayRTArrayStructVectorElem) {
+ // Now add an additional level of arrays around the Block-decorated struct.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i", "ssbo_s"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 32\n"
+ << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n"
+ << "OpMemberDecorate %rtelem 1 Offset 16\n"
+ << TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %v4float = OpTypeVector %float 4
+ %rtelem = OpTypeStruct %v4float %v4float
+ %rtarr = OpTypeRuntimeArray %rtelem
+ %ssbo_s = OpTypeStruct %int %int %rtarr
+ %arr_size = OpConstant %int 10
+ %arr_ssbo = OpTypeArray %ssbo_s %arr_size
+ %var_ty = OpTypePointer Uniform %arr_ssbo
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %int_17 = OpConstant %int 17
+ %i = OpUndef %int
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %[[ssbo_p:\w+]] = OpTypePointer Uniform %ssbo_s
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK-DAG: %int_9 = OpConstant %int 9
+ ; CHECK: OpLabel
+ ; This access chain is manufatured only so we can compute the array length.
+ ; Note that the %int_9 is already clamped
+ ; CHECK: %[[ssbo_base:\w+]] = )" << ac
+ << R"( %[[ssbo_p]] %var %int_9
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %[[ssbo_base]] 2
+ ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
+ ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %[[max]]
+ )" << MainPrefix()
+ << ACCheck(ac, "%int_17 %int_2 %i %int_1 %int_2",
+ "%int_9 %int_2 %[[clamp_i]] %int_1 %int_2")
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest, ACSplitACArrayRTArrayStructVectorElem) {
+ // Split the address calculation across two access chains. Force
+ // the transform to walk up the access chains to find the base variable.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i", "j", "k", "ssbo_s", "ssbo_pty",
+ "rtarr_pty", "ac_ssbo", "ac_rtarr"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 32\n"
+ << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n"
+ << "OpMemberDecorate %rtelem 1 Offset 16\n"
+ << TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %v4float = OpTypeVector %float 4
+ %rtelem = OpTypeStruct %v4float %v4float
+ %rtarr = OpTypeRuntimeArray %rtelem
+ %ssbo_s = OpTypeStruct %int %int %rtarr
+ %arr_size = OpConstant %int 10
+ %arr_ssbo = OpTypeArray %ssbo_s %arr_size
+ %var_ty = OpTypePointer Uniform %arr_ssbo
+ %ssbo_pty = OpTypePointer Uniform %ssbo_s
+ %rtarr_pty = OpTypePointer Uniform %rtarr
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %i = OpUndef %int
+ %j = OpUndef %int
+ %k = OpUndef %int
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK-DAG: %int_9 = OpConstant %int 9
+ ; CHECK-DAG: %int_3 = OpConstant %int 3
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_9
+ ; CHECK: %ac_ssbo = )" << ac
+ << R"( %ssbo_pty %var %[[clamp_i]]
+ ; CHECK: %ac_rtarr = )"
+ << ac << R"( %rtarr_pty %ac_ssbo %int_2
+
+ ; This is the interesting bit. This array length is needed for an OpAccessChain
+ ; computing %ac, but the algorithm had to track back through %ac_rtarr's
+ ; definition to find the base pointer %ac_ssbo.
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %ac_ssbo 2
+ ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
+ ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %j %int_0 %[[max]]
+ ; CHECK: %[[clamp_k:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %k %int_0 %int_3
+ ; CHECK: %ac = )" << ac
+ << R"( %ptr_ty %ac_rtarr %[[clamp_j]] %int_1 %[[clamp_k]]
+ ; CHECK-NOT: AccessChain
+ )" << MainPrefix()
+ << "%ac_ssbo = " << ac << " %ssbo_pty %var %i\n"
+ << "%ac_rtarr = " << ac << " %rtarr_pty %ac_ssbo %int_2\n"
+ << "%ac = " << ac << " %ptr_ty %ac_rtarr %j %int_1 %k\n"
+
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+TEST_F(GraphicsRobustAccessTest,
+ ACSplitACArrayRTArrayStructVectorElemAcrossBasicBlocks) {
+ // Split the address calculation across two access chains. Force
+ // the transform to walk up the access chains to find the base variable.
+ // This time, put the different access chains in different basic blocks.
+ // This sanity checks that we keep the instruction-to-block mapping
+ // consistent.
+ for (auto* ac : AccessChains()) {
+ std::ostringstream shaders;
+ shaders << ShaderPreambleAC({"i", "j", "k", "bb1", "bb2", "ssbo_s",
+ "ssbo_pty", "rtarr_pty", "ac_ssbo",
+ "ac_rtarr"})
+ << "OpMemberDecorate %ssbo_s 0 ArrayStride 32\n"
+ << DecoSSBO() << "OpMemberDecorate %rtelem 0 Offset 0\n"
+ << "OpMemberDecorate %rtelem 1 Offset 16\n"
+ << TypesVoid() << TypesInt() << TypesFloat() << R"(
+ %v4float = OpTypeVector %float 4
+ %rtelem = OpTypeStruct %v4float %v4float
+ %rtarr = OpTypeRuntimeArray %rtelem
+ %ssbo_s = OpTypeStruct %int %int %rtarr
+ %arr_size = OpConstant %int 10
+ %arr_ssbo = OpTypeArray %ssbo_s %arr_size
+ %var_ty = OpTypePointer Uniform %arr_ssbo
+ %ssbo_pty = OpTypePointer Uniform %ssbo_s
+ %rtarr_pty = OpTypePointer Uniform %rtarr
+ %ptr_ty = OpTypePointer Uniform %float
+ %var = OpVariable %var_ty Uniform
+ %int_1 = OpConstant %int 1
+ %int_2 = OpConstant %int 2
+ %i = OpUndef %int
+ %j = OpUndef %int
+ %k = OpUndef %int
+ ; CHECK: %[[GLSLSTD450:\w+]] = OpExtInstImport "GLSL.std.450"
+ ; CHECK-DAG: %int_0 = OpConstant %int 0
+ ; CHECK-DAG: %int_9 = OpConstant %int 9
+ ; CHECK-DAG: %int_3 = OpConstant %int 3
+ ; CHECK: OpLabel
+ ; CHECK: %[[clamp_i:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %i %int_0 %int_9
+ ; CHECK: %ac_ssbo = )" << ac
+ << R"( %ssbo_pty %var %[[clamp_i]]
+ ; CHECK: %bb1 = OpLabel
+ ; CHECK: %ac_rtarr = )"
+ << ac << R"( %rtarr_pty %ac_ssbo %int_2
+ ; CHECK: %bb2 = OpLabel
+
+ ; This is the interesting bit. This array length is needed for an OpAccessChain
+ ; computing %ac, but the algorithm had to track back through %ac_rtarr's
+ ; definition to find the base pointer %ac_ssbo.
+ ; CHECK: %[[arrlen:\w+]] = OpArrayLength %uint %ac_ssbo 2
+ ; CHECK: %[[max:\w+]] = OpISub %int %[[arrlen]] %int_1
+ ; CHECK: %[[clamp_j:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %j %int_0 %[[max]]
+ ; CHECK: %[[clamp_k:\w+]] = OpExtInst %int %[[GLSLSTD450]] UClamp %k %int_0 %int_3
+ ; CHECK: %ac = )" << ac
+ << R"( %ptr_ty %ac_rtarr %[[clamp_j]] %int_1 %[[clamp_k]]
+ ; CHECK-NOT: AccessChain
+ )" << MainPrefix()
+ << "%ac_ssbo = " << ac << " %ssbo_pty %var %i\n"
+ << "OpBranch %bb1\n%bb1 = OpLabel\n"
+ << "%ac_rtarr = " << ac << " %rtarr_pty %ac_ssbo %int_2\n"
+ << "OpBranch %bb2\n%bb2 = OpLabel\n"
+ << "%ac = " << ac << " %ptr_ty %ac_rtarr %j %int_1 %k\n"
+
+ << MainSuffix();
+ SinglePassRunAndMatch<GraphicsRobustAccessPass>(shaders.str(), true);
+ }
+}
+
+// TODO(dneto): Test access chain index wider than 64 bits?
+// TODO(dneto): Test struct access chain index wider than 64 bits?
+// TODO(dneto): OpImageTexelPointer
+// - all Dim types: 1D 2D Cube 3D Rect Buffer
+// - all Dim types that can be arrayed: 1D 2D 3D
+// - sample index: set to 0 if not multisampled
+// - Dim (2D, Cube Rect} with multisampling
+// -1 0 max excess
+// TODO(dneto): Test OpImageTexelPointer with coordinate component index other
+// than 32 bits.
+
+} // namespace
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index cce1053..8dcf933 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -206,6 +206,11 @@
Freeze the values of specialization constants to their default
values.)");
printf(R"(
+ --graphics-robust-access
+ Clamp indices used to access buffers and internal composite
+ values, providing guarantees that satisfy Vulkan's
+ robustBufferAccess rules.)");
+ printf(R"(
--generate-webgpu-initializers
Adds initial values to OpVariable instructions that are missing
them, due to their storage type requiring them for WebGPU.)");