// 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 robustBufferAccess 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 originates 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. // // - Access chain indices are always treated as signed, so: // - Clamp the upper bound at the signed integer maximum. // - Use SClamp for all clamping. // // - 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 <functional> #include <initializer_list> #include <utility> #include "function.h" #include "ir_context.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 "type_manager.h" #include "types.h" namespace spvtools { namespace opt { Instruction; Operand; MakeUnique; GraphicsRobustAccessPass::GraphicsRobustAccessPass() : … { … } Pass::Status GraphicsRobustAccessPass::Process() { … } spvtools::DiagnosticStream GraphicsRobustAccessPass::Fail() { … } spv_result_t GraphicsRobustAccessPass::IsCompatibleModule() { … } spv_result_t GraphicsRobustAccessPass::ProcessCurrentModule() { … } bool GraphicsRobustAccessPass::ProcessAFunction(opt::Function* function) { … } void GraphicsRobustAccessPass::ClampIndicesForAccessChain( Instruction* access_chain) { … } uint32_t GraphicsRobustAccessPass::GetGlslInsts() { … } opt::Instruction* opt::GraphicsRobustAccessPass::GetValueForType( uint64_t value, const analysis::Integer* type) { … } opt::Instruction* opt::GraphicsRobustAccessPass::WidenInteger( bool sign_extend, uint32_t bit_width, Instruction* value, Instruction* before_inst) { … } Instruction* GraphicsRobustAccessPass::MakeUMinInst( const analysis::TypeManager& tm, Instruction* x, Instruction* y, Instruction* where) { … } Instruction* GraphicsRobustAccessPass::MakeSClampInst( const analysis::TypeManager& tm, Instruction* x, Instruction* min, Instruction* max, Instruction* where) { … } Instruction* GraphicsRobustAccessPass::MakeRuntimeArrayLengthInst( Instruction* access_chain, uint32_t operand_index) { … } spv_result_t GraphicsRobustAccessPass::ClampCoordinateForImageTexelPointer( opt::Instruction* image_texel_pointer) { … } opt::Instruction* GraphicsRobustAccessPass::InsertInst( opt::Instruction* where_inst, spv::Op opcode, uint32_t type_id, uint32_t result_id, const Instruction::OperandList& operands) { … } } // namespace opt } // namespace spvtools