Updated spirv-tools.

This commit is contained in:
Бранимир Караџић 2019-02-09 12:43:10 -08:00
parent c3405c13ff
commit a91c5a877e
37 changed files with 1741 additions and 351 deletions

View File

@ -98,6 +98,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/dominator_tree.cpp \
source/opt/eliminate_dead_constant_pass.cpp \
source/opt/eliminate_dead_functions_pass.cpp \
source/opt/eliminate_dead_functions_util.cpp \
source/opt/feature_manager.cpp \
source/opt/flatten_decoration_pass.cpp \
source/opt/fold.cpp \

View File

@ -496,6 +496,8 @@ static_library("spvtools_opt") {
"source/opt/eliminate_dead_constant_pass.h",
"source/opt/eliminate_dead_functions_pass.cpp",
"source/opt/eliminate_dead_functions_pass.h",
"source/opt/eliminate_dead_functions_util.cpp",
"source/opt/eliminate_dead_functions_util.h",
"source/opt/feature_manager.cpp",
"source/opt/feature_manager.h",
"source/opt/flatten_decoration_pass.cpp",

View File

@ -432,6 +432,18 @@ The validator operates on the binary form.
* `spirv-val` - the standalone validator
* `<spirv-dir>/tools/val`
### Reducer tool
The reducer shrinks a SPIR-V binary module, guided by a user-supplied
*interestingness test*.
This is a work in progress, with initially only shrinks a module in a few ways.
* `spirv-reduce` - the standalone reducer
* `<spirv-dir>/tools/reduce`
Run `spirv-reduce --help` to see how to specify interestingness.
### Control flow dumper tool
The control flow dumper prints the control flow graph for a SPIR-V module as a

View File

@ -1 +1 @@
"v2019.2-dev", "SPIRV-Tools v2019.2-dev 6bc0c3fb0a18b013fc222ac6173abaff22f65a39"
"v2019.2-dev", "SPIRV-Tools v2019.2-dev c3405c13ff287fcc881a3244dafd761d40edaa50"

View File

@ -30,17 +30,17 @@ namespace spvtools {
// Stream Output Buffer Offsets
//
// The following values provide 32-bit word offsets into the output buffer
// The following values provide offsets into the output buffer struct
// generated by InstrumentPass::GenDebugStreamWrite. This method is utilized
// by InstBindlessCheckPass.
//
// The first word of the debug output buffer contains the next available word
// The first member of the debug output buffer contains the next available word
// in the data stream to be written. Shaders will atomically read and update
// this value so as not to overwrite each others records. This value must be
// initialized to zero
static const int kDebugOutputSizeOffset = 0;
// The second word of the output buffer is the start of the stream of records
// The second member of the output buffer is the start of the stream of records
// written by the instrumented shaders. Each record represents a validation
// error. The format of the records is documented below.
static const int kDebugOutputDataOffset = 1;
@ -122,14 +122,45 @@ static const int kInstMaxOutCnt = kInstStageOutCnt + 3;
// These are the possible validation error codes.
static const int kInstErrorBindlessBounds = 0;
// Direct Input Buffer Offsets
//
// The following values provide member offsets into the input buffers
// consumed by InstrumentPass::GenDebugDirectRead(). This method is utilized
// by InstBindlessCheckPass.
//
// The only object in an input buffer is a runtime array of unsigned
// integers. Each validation will have its own formatting of this array.
static const int kDebugInputDataOffset = 0;
// Debug Buffer Bindings
//
// These are the bindings for the different buffers which are
// read or written by the instrumentation passes.
//
// This is the output buffer written by InstBindlessCheckPass.
// This is the output buffer written by InstBindlessCheckPass
// and possibly other future validations.
static const int kDebugOutputBindingStream = 0;
// The binding for the input buffer for InstBindlessCheckPass. The input
// buffer needs only be created if the shaders being validated contain a
// descriptor array of runtime size, and validation of runtime size descriptor
// arrays have been enabled at the time of the bindless validation pass
// creation.
static const int kDebugInputBindingBindless = 1;
// Bindless Validation Input Buffer Format
//
// An input buffer for bindless validation consists of a single array of
// unsigned integers we will call Data[]. This array is formatted as follows.
//
// At the beginning of the array is a single uint reserved for future use.
static const int kDebugInputBindlessOffsetReserved = 0;
// Following the reserved uint is some number of uints such that the following
// is true: the number of descriptors at (set=s, binding=b) is:
// Data[ Data[ s + kDebugInputBindlessOffsetLengths ] + b ]
static const int kDebugInputBindlessOffsetLengths = 1;
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_INSTRUMENT_HPP_

View File

@ -445,6 +445,10 @@ typedef enum {
// Returns a string describing the given SPIR-V target environment.
SPIRV_TOOLS_EXPORT const char* spvTargetEnvDescription(spv_target_env env);
// Parses s into *env and returns true if successful. If unparsable, returns
// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
SPIRV_TOOLS_EXPORT bool spvParseTargetEnv(const char* s, spv_target_env* env);
// Creates a context object. Returns null if env is invalid.
SPIRV_TOOLS_EXPORT spv_context spvContextCreate(spv_target_env env);

View File

@ -711,10 +711,12 @@ Optimizer::PassToken CreateCombineAccessChainsPass();
// The instrumentation will read and write buffers in debug
// descriptor set |desc_set|. It will write |shader_id| in each output record
// to identify the shader module which generated the record.
// |runtime_array_enable| controls instrumentation of runtime arrays which
// require input buffer support.
//
// TODO(greg-lunarg): Add support for vk_ext_descriptor_indexing.
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
uint32_t shader_id);
Optimizer::PassToken CreateInstBindlessCheckPass(
uint32_t desc_set, uint32_t shader_id, bool runtime_array_enable = false);
// Create a pass to upgrade to the VulkanKHR memory model.
// This pass upgrades the Logical GLSL450 memory model to Logical VulkanKHR.

View File

@ -37,7 +37,7 @@ cd $SHADERC_DIR/third_party
# Get shaderc dependencies. Link the appropriate SPIRV-Tools.
git clone https://github.com/google/googletest.git
git clone https://github.com/google/glslang.git
git clone https://github.com/KhronosGroup/glslang.git
ln -s $GITHUB_DIR/SPIRV-Tools spirv-tools
git clone https://github.com/KhronosGroup/SPIRV-Headers.git spirv-headers
git clone https://github.com/google/re2

View File

@ -37,6 +37,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
dominator_tree.h
eliminate_dead_constant_pass.h
eliminate_dead_functions_pass.h
eliminate_dead_functions_util.h
feature_manager.h
flatten_decoration_pass.h
fold.h
@ -131,6 +132,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
dominator_tree.cpp
eliminate_dead_constant_pass.cpp
eliminate_dead_functions_pass.cpp
eliminate_dead_functions_util.cpp
feature_manager.cpp
flatten_decoration_pass.cpp
fold.cpp

View File

@ -13,6 +13,7 @@
// limitations under the License.
#include "source/opt/eliminate_dead_functions_pass.h"
#include "source/opt/eliminate_dead_functions_util.h"
#include <unordered_set>
@ -36,8 +37,8 @@ Pass::Status EliminateDeadFunctionsPass::Process() {
funcIter != get_module()->end();) {
if (live_function_set.count(&*funcIter) == 0) {
modified = true;
EliminateFunction(&*funcIter);
funcIter = funcIter.Erase();
funcIter =
eliminatedeadfunctionsutil::EliminateFunction(context(), &funcIter);
} else {
++funcIter;
}
@ -47,10 +48,5 @@ Pass::Status EliminateDeadFunctionsPass::Process() {
: Pass::Status::SuccessWithoutChange;
}
void EliminateDeadFunctionsPass::EliminateFunction(Function* func) {
// Remove all of the instruction in the function body
func->ForEachInst([this](Instruction* inst) { context()->KillInst(inst); },
true);
}
} // namespace opt
} // namespace spvtools

View File

@ -0,0 +1,32 @@
// 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 "eliminate_dead_functions_util.h"
namespace spvtools {
namespace opt {
namespace eliminatedeadfunctionsutil {
Module::iterator EliminateFunction(IRContext* context,
Module::iterator* func_iter) {
(*func_iter)
->ForEachInst([context](Instruction* inst) { context->KillInst(inst); },
true);
return func_iter->Erase();
}
} // namespace eliminatedeadfunctionsutil
} // namespace opt
} // namespace spvtools

View File

@ -0,0 +1,36 @@
// 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_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
#define SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_
#include "source/opt/ir_context.h"
namespace spvtools {
namespace opt {
// Provides functionality for eliminating functions that are not needed, for use
// by various analyses and passes.
namespace eliminatedeadfunctionsutil {
// Removes all of the function's instructions, removes the function from the
// module, and returns the next iterator.
Module::iterator EliminateFunction(IRContext* context,
Module::iterator* func_iter);
} // namespace eliminatedeadfunctionsutil
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_ELIMINATE_DEAD_FUNCTIONS_UTIL_H_

View File

@ -35,6 +35,18 @@ static const int kSpvConstantValueInIdx = 0;
namespace spvtools {
namespace opt {
uint32_t InstBindlessCheckPass::GenDebugReadLength(
uint32_t image_id, InstructionBuilder* builder) {
uint32_t desc_set_idx =
var2desc_set_[image_id] + kDebugInputBindlessOffsetLengths;
uint32_t desc_set_idx_id = builder->GetUintConstantId(desc_set_idx);
uint32_t desc_set_offset_id = GenDebugDirectRead(desc_set_idx_id, builder);
Instruction* binding_idx_inst =
builder->AddBinaryOp(GetUintId(), SpvOpIAdd, desc_set_offset_id,
builder->GetUintConstantId(var2binding_[image_id]));
return GenDebugDirectRead(binding_idx_inst->result_id(), builder);
}
void InstBindlessCheckPass::GenBindlessCheckCode(
BasicBlock::iterator ref_inst_itr,
UptrVectorIterator<BasicBlock> ref_block_itr, uint32_t instruction_idx,
@ -119,20 +131,23 @@ void InstBindlessCheckPass::GenBindlessCheckCode(
uint32_t ptr_type_id =
var_type_inst->GetSingleWordInOperand(kSpvTypePointerTypeIdInIdx);
Instruction* ptr_type_inst = get_def_use_mgr()->GetDef(ptr_type_id);
// TODO(greg-lunarg): Handle RuntimeArray. Will need to pull length
// out of debug input buffer.
if (ptr_type_inst->opcode() != SpvOpTypeArray) return;
// If index and bound both compile-time constants and index < bound,
// return without changing
uint32_t length_id =
ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
if (index_inst->opcode() == SpvOpConstant &&
length_inst->opcode() == SpvOpConstant &&
index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
uint32_t length_id = 0;
if (ptr_type_inst->opcode() == SpvOpTypeArray) {
length_id =
ptr_type_inst->GetSingleWordInOperand(kSpvTypeArrayLengthIdInIdx);
Instruction* index_inst = get_def_use_mgr()->GetDef(index_id);
Instruction* length_inst = get_def_use_mgr()->GetDef(length_id);
if (index_inst->opcode() == SpvOpConstant &&
length_inst->opcode() == SpvOpConstant &&
index_inst->GetSingleWordInOperand(kSpvConstantValueInIdx) <
length_inst->GetSingleWordInOperand(kSpvConstantValueInIdx))
return;
} else if (!runtime_array_enabled_ ||
ptr_type_inst->opcode() != SpvOpTypeRuntimeArray) {
return;
}
// Generate full runtime bounds test code with true branch
// being full reference and false branch being debug output and zero
// for the referenced value.
@ -141,6 +156,13 @@ void InstBindlessCheckPass::GenBindlessCheckCode(
context(), &*new_blk_ptr,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
uint32_t error_id = builder.GetUintConstantId(kInstErrorBindlessBounds);
// If length id not yet set, descriptor array is runtime size so
// generate load of length from stage's debug input buffer.
if (length_id == 0) {
assert(ptr_type_inst->opcode() == SpvOpTypeRuntimeArray &&
"unexpected bindless type");
length_id = GenDebugReadLength(image_id, &builder);
}
Instruction* ult_inst =
builder.AddBinaryOp(GetBoolId(), SpvOpULessThan, index_id, length_id);
uint32_t merge_blk_id = TakeNextId();
@ -236,6 +258,18 @@ void InstBindlessCheckPass::InitializeInstBindlessCheck() {
break;
}
}
// If descriptor indexing extension and runtime array support enabled,
// create variable to descriptor set mapping.
if (ext_descriptor_indexing_defined_ && runtime_array_enabled_)
for (auto& anno : get_module()->annotations())
if (anno.opcode() == SpvOpDecorate) {
if (anno.GetSingleWordInOperand(1u) == SpvDecorationDescriptorSet)
var2desc_set_[anno.GetSingleWordInOperand(0u)] =
anno.GetSingleWordInOperand(2u);
else if (anno.GetSingleWordInOperand(1u) == SpvDecorationBinding)
var2binding_[anno.GetSingleWordInOperand(0u)] =
anno.GetSingleWordInOperand(2u);
}
}
Pass::Status InstBindlessCheckPass::ProcessImpl() {

View File

@ -29,10 +29,14 @@ namespace opt {
class InstBindlessCheckPass : public InstrumentPass {
public:
// For test harness only
InstBindlessCheckPass() : InstrumentPass(7, 23, kInstValidationIdBindless) {}
InstBindlessCheckPass()
: InstrumentPass(7, 23, kInstValidationIdBindless),
runtime_array_enabled_(true) {}
// For all other interfaces
InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id)
: InstrumentPass(desc_set, shader_id, kInstValidationIdBindless) {}
InstBindlessCheckPass(uint32_t desc_set, uint32_t shader_id,
bool runtime_array_enable)
: InstrumentPass(desc_set, shader_id, kInstValidationIdBindless),
runtime_array_enabled_(runtime_array_enable) {}
~InstBindlessCheckPass() override = default;
@ -42,6 +46,10 @@ class InstBindlessCheckPass : public InstrumentPass {
const char* name() const override { return "inst-bindless-check-pass"; }
private:
// Generate instructions into |builder| to read length of runtime descriptor
// array |image_id| from debug input buffer and return id of value.
uint32_t GenDebugReadLength(uint32_t image_id, InstructionBuilder* builder);
// Initialize state for instrumenting bindless checking
void InitializeInstBindlessCheck();
@ -85,6 +93,15 @@ class InstBindlessCheckPass : public InstrumentPass {
// True if VK_EXT_descriptor_indexing is defined
bool ext_descriptor_indexing_defined_;
// Enable instrumentation of runtime arrays
bool runtime_array_enabled_;
// Mapping from variable to descriptor set
std::unordered_map<uint32_t, uint32_t> var2desc_set_;
// Mapping from variable to binding
std::unordered_map<uint32_t, uint32_t> var2binding_;
};
} // namespace opt

View File

@ -107,7 +107,7 @@ void InstrumentPass::GenDebugOutputFieldCode(uint32_t base_offset_id,
builder->AddBinaryOp(GetUintId(), SpvOpIAdd, base_offset_id,
builder->GetUintConstantId(field_offset));
uint32_t buf_id = GetOutputBufferId();
uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
Instruction* achain_inst =
builder->AddTernaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
builder->GetUintConstantId(kDebugOutputDataOffset),
@ -222,6 +222,18 @@ void InstrumentPass::GenDebugStreamWrite(
(void)builder->AddNaryOp(GetVoidId(), SpvOpFunctionCall, args);
}
uint32_t InstrumentPass::GenDebugDirectRead(uint32_t idx_id,
InstructionBuilder* builder) {
uint32_t input_buf_id = GetInputBufferId();
uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
Instruction* ibuf_ac_inst = builder->AddTernaryOp(
buf_uint_ptr_id, SpvOpAccessChain, input_buf_id,
builder->GetUintConstantId(kDebugInputDataOffset), idx_id);
Instruction* load_inst =
builder->AddUnaryOp(GetUintId(), SpvOpLoad, ibuf_ac_inst->result_id());
return load_inst->result_id();
}
bool InstrumentPass::IsSameBlockOp(const Instruction* inst) const {
return inst->opcode() == SpvOpSampledImage || inst->opcode() == SpvOpImage;
}
@ -280,12 +292,12 @@ void InstrumentPass::UpdateSucceedingPhis(
}
// Return id for output buffer uint ptr type
uint32_t InstrumentPass::GetOutputBufferUintPtrId() {
if (output_buffer_uint_ptr_id_ == 0) {
output_buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
uint32_t InstrumentPass::GetBufferUintPtrId() {
if (buffer_uint_ptr_id_ == 0) {
buffer_uint_ptr_id_ = context()->get_type_mgr()->FindPointerToType(
GetUintId(), SpvStorageClassStorageBuffer);
}
return output_buffer_uint_ptr_id_;
return buffer_uint_ptr_id_;
}
uint32_t InstrumentPass::GetOutputBufferBinding() {
@ -298,22 +310,74 @@ uint32_t InstrumentPass::GetOutputBufferBinding() {
return 0;
}
uint32_t InstrumentPass::GetInputBufferBinding() {
switch (validation_id_) {
case kInstValidationIdBindless:
return kDebugInputBindingBindless;
default:
assert(false && "unexpected validation id");
}
return 0;
}
analysis::Type* InstrumentPass::GetUintRuntimeArrayType(
analysis::DecorationManager* deco_mgr, analysis::TypeManager* type_mgr) {
if (uint_rarr_ty_ == nullptr) {
analysis::Integer uint_ty(32, false);
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
analysis::RuntimeArray uint_rarr_ty_tmp(reg_uint_ty);
uint_rarr_ty_ = type_mgr->GetRegisteredType(&uint_rarr_ty_tmp);
uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(uint_rarr_ty_);
// By the Vulkan spec, a pre-existing RuntimeArray of uint must be part of
// a block, and will therefore be decorated with an ArrayStride. Therefore
// the undecorated type returned here will not be pre-existing and can
// safely be decorated. Since this type is now decorated, it is out of
// sync with the TypeManager and therefore the TypeManager must be
// invalidated after this pass.
assert(context()->get_def_use_mgr()->NumUses(uint_arr_ty_id) == 0 &&
"used RuntimeArray type returned");
deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
}
return uint_rarr_ty_;
}
void InstrumentPass::AddStorageBufferExt() {
if (storage_buffer_ext_defined_) return;
if (!get_feature_mgr()->HasExtension(kSPV_KHR_storage_buffer_storage_class)) {
const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
const auto num_chars = ext_name.size();
// Compute num words, accommodate the terminating null character.
const auto num_words = (num_chars + 1 + 3) / 4;
std::vector<uint32_t> ext_words(num_words, 0u);
std::memcpy(ext_words.data(), ext_name.data(), num_chars);
context()->AddExtension(std::unique_ptr<Instruction>(
new Instruction(context(), SpvOpExtension, 0u, 0u,
{{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
}
storage_buffer_ext_defined_ = true;
}
// Return id for output buffer
uint32_t InstrumentPass::GetOutputBufferId() {
if (output_buffer_id_ == 0) {
// If not created yet, create one
analysis::DecorationManager* deco_mgr = get_decoration_mgr();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Type* reg_uint_rarr_ty =
GetUintRuntimeArrayType(deco_mgr, type_mgr);
analysis::Integer uint_ty(32, false);
analysis::Type* reg_uint_ty = type_mgr->GetRegisteredType(&uint_ty);
analysis::RuntimeArray uint_rarr_ty(reg_uint_ty);
analysis::Type* reg_uint_rarr_ty =
type_mgr->GetRegisteredType(&uint_rarr_ty);
uint32_t uint_arr_ty_id = type_mgr->GetTypeInstruction(reg_uint_rarr_ty);
deco_mgr->AddDecorationVal(uint_arr_ty_id, SpvDecorationArrayStride, 4u);
analysis::Struct obuf_ty({reg_uint_ty, reg_uint_rarr_ty});
analysis::Type* reg_obuf_ty = type_mgr->GetRegisteredType(&obuf_ty);
uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_obuf_ty);
analysis::Struct buf_ty({reg_uint_ty, reg_uint_rarr_ty});
analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
uint32_t obufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
// By the Vulkan spec, a pre-existing struct containing a RuntimeArray
// must be a block, and will therefore be decorated with Block. Therefore
// the undecorated type returned here will not be pre-existing and can
// safely be decorated. Since this type is now decorated, it is out of
// sync with the TypeManager and therefore the TypeManager must be
// invalidated after this pass.
assert(context()->get_def_use_mgr()->NumUses(obufTyId) == 0 &&
"used struct type returned");
deco_mgr->AddDecoration(obufTyId, SpvDecorationBlock);
deco_mgr->AddMemberDecoration(obufTyId, kDebugOutputSizeOffset,
SpvDecorationOffset, 0);
@ -331,23 +395,48 @@ uint32_t InstrumentPass::GetOutputBufferId() {
desc_set_);
deco_mgr->AddDecorationVal(output_buffer_id_, SpvDecorationBinding,
GetOutputBufferBinding());
// Look for storage buffer extension. If none, create one.
if (!get_feature_mgr()->HasExtension(
kSPV_KHR_storage_buffer_storage_class)) {
const std::string ext_name("SPV_KHR_storage_buffer_storage_class");
const auto num_chars = ext_name.size();
// Compute num words, accommodate the terminating null character.
const auto num_words = (num_chars + 1 + 3) / 4;
std::vector<uint32_t> ext_words(num_words, 0u);
std::memcpy(ext_words.data(), ext_name.data(), num_chars);
context()->AddExtension(std::unique_ptr<Instruction>(
new Instruction(context(), SpvOpExtension, 0u, 0u,
{{SPV_OPERAND_TYPE_LITERAL_STRING, ext_words}})));
}
AddStorageBufferExt();
}
return output_buffer_id_;
}
uint32_t InstrumentPass::GetInputBufferId() {
if (input_buffer_id_ == 0) {
// If not created yet, create one
analysis::DecorationManager* deco_mgr = get_decoration_mgr();
analysis::TypeManager* type_mgr = context()->get_type_mgr();
analysis::Type* reg_uint_rarr_ty =
GetUintRuntimeArrayType(deco_mgr, type_mgr);
analysis::Struct buf_ty({reg_uint_rarr_ty});
analysis::Type* reg_buf_ty = type_mgr->GetRegisteredType(&buf_ty);
uint32_t ibufTyId = type_mgr->GetTypeInstruction(reg_buf_ty);
// By the Vulkan spec, a pre-existing struct containing a RuntimeArray
// must be a block, and will therefore be decorated with Block. Therefore
// the undecorated type returned here will not be pre-existing and can
// safely be decorated. Since this type is now decorated, it is out of
// sync with the TypeManager and therefore the TypeManager must be
// invalidated after this pass.
assert(context()->get_def_use_mgr()->NumUses(ibufTyId) == 0 &&
"used struct type returned");
deco_mgr->AddDecoration(ibufTyId, SpvDecorationBlock);
deco_mgr->AddMemberDecoration(ibufTyId, 0, SpvDecorationOffset, 0);
uint32_t ibufTyPtrId_ =
type_mgr->FindPointerToType(ibufTyId, SpvStorageClassStorageBuffer);
input_buffer_id_ = TakeNextId();
std::unique_ptr<Instruction> newVarOp(new Instruction(
context(), SpvOpVariable, ibufTyPtrId_, input_buffer_id_,
{{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{SpvStorageClassStorageBuffer}}}));
context()->AddGlobalValue(std::move(newVarOp));
deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationDescriptorSet,
desc_set_);
deco_mgr->AddDecorationVal(input_buffer_id_, SpvDecorationBinding,
GetInputBufferBinding());
AddStorageBufferExt();
}
return input_buffer_id_;
}
uint32_t InstrumentPass::GetVec4FloatId() {
if (v4float_id_ == 0) {
analysis::TypeManager* type_mgr = context()->get_type_mgr();
@ -447,7 +536,7 @@ uint32_t InstrumentPass::GetStreamWriteFunctionId(uint32_t stage_idx,
// Gen test if debug output buffer size will not be exceeded.
uint32_t obuf_record_sz = kInstStageOutCnt + val_spec_param_cnt;
uint32_t buf_id = GetOutputBufferId();
uint32_t buf_uint_ptr_id = GetOutputBufferUintPtrId();
uint32_t buf_uint_ptr_id = GetBufferUintPtrId();
Instruction* obuf_curr_sz_ac_inst =
builder.AddBinaryOp(buf_uint_ptr_id, SpvOpAccessChain, buf_id,
builder.GetUintConstantId(kDebugOutputSizeOffset));
@ -620,14 +709,17 @@ bool InstrumentPass::InstProcessEntryPointCallTree(InstProcessFunction& pfn) {
void InstrumentPass::InitializeInstrument() {
output_buffer_id_ = 0;
output_buffer_uint_ptr_id_ = 0;
buffer_uint_ptr_id_ = 0;
output_func_id_ = 0;
output_func_param_cnt_ = 0;
input_buffer_id_ = 0;
v4float_id_ = 0;
uint_id_ = 0;
v4uint_id_ = 0;
bool_id_ = 0;
void_id_ = 0;
storage_buffer_ext_defined_ = false;
uint_rarr_ty_ = nullptr;
// clear collections
id2function_.clear();

View File

@ -76,7 +76,7 @@ class InstrumentPass : public Pass {
IRContext::kAnalysisInstrToBlockMapping |
IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
IRContext::kAnalysisNameMap | IRContext::kAnalysisBuiltinVarId |
IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
IRContext::kAnalysisConstants;
}
protected:
@ -195,6 +195,13 @@ class InstrumentPass : public Pass {
const std::vector<uint32_t>& validation_ids,
InstructionBuilder* builder);
// Generate in |builder| instructions to read the unsigned integer from the
// input buffer at offset |idx_id|. Return the result id.
//
// The binding and the format of the input buffer is determined by each
// specific validation, which is specified at the creation of the pass.
uint32_t GenDebugDirectRead(uint32_t idx_id, InstructionBuilder* builder);
// Generate code to cast |value_id| to unsigned, if needed. Return
// an id to the unsigned equivalent.
uint32_t GenUintCastCode(uint32_t value_id, InstructionBuilder* builder);
@ -211,15 +218,28 @@ class InstrumentPass : public Pass {
// Return id for void type
uint32_t GetVoidId();
// Return id for output buffer uint type
uint32_t GetOutputBufferUintPtrId();
// Return pointer to type for runtime array of uint
analysis::Type* GetUintRuntimeArrayType(analysis::DecorationManager* deco_mgr,
analysis::TypeManager* type_mgr);
// Return id for buffer uint type
uint32_t GetBufferUintPtrId();
// Return binding for output buffer for current validation.
uint32_t GetOutputBufferBinding();
// Return binding for input buffer for current validation.
uint32_t GetInputBufferBinding();
// Add storage buffer extension if needed
void AddStorageBufferExt();
// Return id for debug output buffer
uint32_t GetOutputBufferId();
// Return id for debug input buffer
uint32_t GetInputBufferId();
// Return id for v4float type
uint32_t GetVec4FloatId();
@ -321,7 +341,7 @@ class InstrumentPass : public Pass {
uint32_t output_buffer_id_;
// type id for output buffer element
uint32_t output_buffer_uint_ptr_id_;
uint32_t buffer_uint_ptr_id_;
// id for debug output function
uint32_t output_func_id_;
@ -329,6 +349,9 @@ class InstrumentPass : public Pass {
// param count for output function
uint32_t output_func_param_cnt_;
// id for input buffer variable
uint32_t input_buffer_id_;
// id for v4float type
uint32_t v4float_id_;
@ -344,6 +367,12 @@ class InstrumentPass : public Pass {
// id for void type
uint32_t void_id_;
// boolean to remember storage buffer extension
bool storage_buffer_ext_defined_;
// runtime array of uint type
analysis::Type* uint_rarr_ty_;
// Pre-instrumentation same-block insts
std::unordered_map<uint32_t, Instruction*> same_block_pre_;

View File

@ -57,8 +57,8 @@ Optimizer::PassToken::~PassToken() {}
struct Optimizer::Impl {
explicit Impl(spv_target_env env) : target_env(env), pass_manager() {}
spv_target_env target_env; // Target environment.
opt::PassManager pass_manager; // Internal implementation pass manager.
spv_target_env target_env; // Target environment.
opt::PassManager pass_manager; // Internal implementation pass manager.
};
Optimizer::Optimizer(spv_target_env env) : impl_(new Impl(env)) {}
@ -218,7 +218,9 @@ Optimizer& Optimizer::RegisterSizePasses() {
}
Optimizer& Optimizer::RegisterWebGPUPasses() {
return RegisterPass(CreateAggressiveDCEPass())
return RegisterPass(CreateStripDebugInfoPass())
.RegisterPass(CreateFlattenDecorationPass())
.RegisterPass(CreateAggressiveDCEPass())
.RegisterPass(CreateDeadBranchElimPass());
}
@ -379,7 +381,7 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
} else if (pass_name == "replace-invalid-opcode") {
RegisterPass(CreateReplaceInvalidOpcodePass());
} else if (pass_name == "inst-bindless-check") {
RegisterPass(CreateInstBindlessCheckPass(7, 23));
RegisterPass(CreateInstBindlessCheckPass(7, 23, true));
RegisterPass(CreateSimplificationPass());
RegisterPass(CreateDeadBranchElimPass());
RegisterPass(CreateBlockMergePass());
@ -788,9 +790,11 @@ Optimizer::PassToken CreateUpgradeMemoryModelPass() {
}
Optimizer::PassToken CreateInstBindlessCheckPass(uint32_t desc_set,
uint32_t shader_id) {
uint32_t shader_id,
bool runtime_array_enable) {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id));
MakeUnique<opt::InstBindlessCheckPass>(desc_set, shader_id,
runtime_array_enable));
}
Optimizer::PassToken CreateCodeSinkingPass() {

View File

@ -25,6 +25,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
reduction_pass.h
reduction_util.h
remove_instruction_reduction_opportunity.h
remove_function_reduction_opportunity.h
remove_function_reduction_opportunity_finder.h
remove_opname_instruction_reduction_opportunity_finder.h
remove_unreferenced_instruction_reduction_opportunity_finder.h
structured_loop_to_selection_reduction_opportunity.h
@ -41,6 +43,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
reduction_opportunity.cpp
reduction_pass.cpp
reduction_util.cpp
remove_function_reduction_opportunity.cpp
remove_function_reduction_opportunity_finder.cpp
remove_instruction_reduction_opportunity.cpp
remove_unreferenced_instruction_reduction_opportunity_finder.cpp
remove_opname_instruction_reduction_opportunity_finder.cpp

View File

@ -0,0 +1,40 @@
// 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 "remove_function_reduction_opportunity.h"
#include "source/opt/eliminate_dead_functions_util.h"
namespace spvtools {
namespace reduce {
bool RemoveFunctionReductionOpportunity::PreconditionHolds() {
// Removing one function cannot influence whether another function can be
// removed.
return true;
}
void RemoveFunctionReductionOpportunity::Apply() {
for (opt::Module::iterator function_it = context_->module()->begin();
function_it != context_->module()->end(); ++function_it) {
if (&*function_it == function_) {
opt::eliminatedeadfunctionsutil::EliminateFunction(context_,
&function_it);
return;
}
}
assert(0 && "Function to be removed was not found.");
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,49 @@
// 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_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_
#include "reduction_opportunity.h"
#include "source/opt/function.h"
namespace spvtools {
namespace reduce {
// An opportunity to remove an unreferenced function.
class RemoveFunctionReductionOpportunity : public ReductionOpportunity {
public:
// Creates an opportunity to remove |function| from the module represented by
// |context|.
RemoveFunctionReductionOpportunity(opt::IRContext* context,
opt::Function* function)
: context_(context), function_(function) {}
bool PreconditionHolds() override;
protected:
void Apply() override;
private:
// The IR context for the module under analysis.
opt::IRContext* context_;
// The function that can be removed.
opt::Function* function_;
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_H_

View File

@ -0,0 +1,42 @@
// 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 "remove_function_reduction_opportunity_finder.h"
#include "remove_function_reduction_opportunity.h"
namespace spvtools {
namespace reduce {
std::vector<std::unique_ptr<ReductionOpportunity>>
RemoveFunctionReductionOpportunityFinder::GetAvailableOpportunities(
opt::IRContext* context) const {
std::vector<std::unique_ptr<ReductionOpportunity>> result;
// Consider each function.
for (auto& function : *context->module()) {
if (context->get_def_use_mgr()->NumUses(function.result_id()) > 0) {
// If the function is referenced, ignore it.
continue;
}
result.push_back(
MakeUnique<RemoveFunctionReductionOpportunity>(context, &function));
}
return result;
}
std::string RemoveFunctionReductionOpportunityFinder::GetName() const {
return "RemoveFunctionReductionOpportunityFinder";
}
} // namespace reduce
} // namespace spvtools

View File

@ -0,0 +1,42 @@
// 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_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
#define SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_
#include "source/reduce/reduction_opportunity_finder.h"
namespace spvtools {
namespace reduce {
// A finder of opportunities to remove unreferenced functions.
class RemoveFunctionReductionOpportunityFinder
: public ReductionOpportunityFinder {
public:
RemoveFunctionReductionOpportunityFinder() = default;
~RemoveFunctionReductionOpportunityFinder() override = default;
std::string GetName() const final;
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
opt::IRContext* context) const final;
private:
};
} // namespace reduce
} // namespace spvtools
#endif // SOURCE_REDUCE_REMOVE_FUNCTION_REDUCTION_OPPORTUNITY_FINDER_H_

View File

@ -19,10 +19,6 @@
#include "spirv-tools/libspirv.h"
// Parses s into *env and returns true if successful. If unparsable, returns
// false and sets *env to SPV_ENV_UNIVERSAL_1_0.
bool spvParseTargetEnv(const char* s, spv_target_env* env);
// Returns true if |env| is a VULKAN environment, false otherwise.
bool spvIsVulkanEnv(spv_target_env env);

View File

@ -247,6 +247,49 @@ spv_result_t ValidateFunctionCall(ValidationState_t& _,
<< "'s type does not match Function <id> '"
<< _.getIdName(parameter_type_id) << "'s parameter type.";
}
if (_.addressing_model() == SpvAddressingModelLogical) {
if (parameter_type->opcode() == SpvOpTypePointer) {
SpvStorageClass sc = parameter_type->GetOperandAs<SpvStorageClass>(1u);
// Validate which storage classes can be pointer operands.
switch (sc) {
case SpvStorageClassUniformConstant:
case SpvStorageClassFunction:
case SpvStorageClassPrivate:
case SpvStorageClassWorkgroup:
case SpvStorageClassAtomicCounter:
// These are always allowed.
break;
case SpvStorageClassStorageBuffer:
if (!_.features().variable_pointers_storage_buffer) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "StorageBuffer pointer operand "
<< _.getIdName(argument_id)
<< " requires a variable pointers capability";
}
break;
default:
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Invalid storage class for pointer operand "
<< _.getIdName(argument_id);
}
// Validate memory object declaration requirements.
if (argument->opcode() != SpvOpVariable &&
argument->opcode() != SpvOpFunctionParameter) {
const bool ssbo_vptr =
_.features().variable_pointers_storage_buffer &&
sc == SpvStorageClassStorageBuffer;
const bool wg_vptr =
_.features().variable_pointers && sc == SpvStorageClassWorkgroup;
if (!ssbo_vptr && !wg_vptr) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "Pointer operand " << _.getIdName(argument_id)
<< " must be a memory object declaration";
}
}
}
}
}
return SPV_SUCCESS;
}

View File

@ -262,17 +262,23 @@ spv_result_t ValidateTypeStruct(ValidationState_t& _, const Instruction* inst) {
spv_result_t ValidateTypePointer(ValidationState_t& _,
const Instruction* inst) {
const auto type_id = inst->GetOperandAs<uint32_t>(2);
const auto type = _.FindDef(type_id);
auto type_id = inst->GetOperandAs<uint32_t>(2);
auto type = _.FindDef(type_id);
if (!type || !spvOpcodeGeneratesType(type->opcode())) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "OpTypePointer Type <id> '" << _.getIdName(type_id)
<< "' is not a type.";
}
// See if this points to a storage image.
if (type->opcode() == SpvOpTypeImage) {
const auto storage_class = inst->GetOperandAs<uint32_t>(1);
if (storage_class == SpvStorageClassUniformConstant) {
const auto storage_class = inst->GetOperandAs<SpvStorageClass>(1);
if (storage_class == SpvStorageClassUniformConstant) {
// Unpack an optional level of arraying.
if (type->opcode() == SpvOpTypeArray ||
type->opcode() == SpvOpTypeRuntimeArray) {
type_id = type->GetOperandAs<uint32_t>(1);
type = _.FindDef(type_id);
}
if (type->opcode() == SpvOpTypeImage) {
const auto sampled = type->GetOperandAs<uint32_t>(6);
// 2 indicates this image is known to be be used without a sampler, i.e.
// a storage image.

View File

@ -630,274 +630,6 @@ OpFunctionEnd
true);
}
TEST_F(InstBindlessTest, ReuseConstsTypesBuiltins) {
// This test verifies that the pass resuses existing constants, types
// and builtin variables. This test was created by editing the SPIR-V
// from the Simple test.
const std::string defs_before =
R"(OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
OpExecutionMode %MainPs OriginUpperLeft
OpSource HLSL 500
OpName %MainPs "MainPs"
OpName %g_tColor "g_tColor"
OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
OpName %_ ""
OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 3
OpDecorate %g_tColor Binding 0
OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
OpDecorate %PerViewConstantBuffer_t Block
OpDecorate %g_sAniso DescriptorSet 0
OpDecorate %i_vTextureCoords Location 0
OpDecorate %_entryPointOutput_vColor Location 0
OpDecorate %85 DescriptorSet 7
OpDecorate %85 Binding 0
OpDecorate %gl_FragCoord BuiltIn FragCoord
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%v4float = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
%uint = OpTypeInt 32 0
%uint_128 = OpConstant %uint 128
%_arr_20_uint_128 = OpTypeArray %20 %uint_128
%_ptr_UniformConstant__arr_20_uint_128 = OpTypePointer UniformConstant %_arr_20_uint_128
%g_tColor = OpVariable %_ptr_UniformConstant__arr_20_uint_128 UniformConstant
%PerViewConstantBuffer_t = OpTypeStruct %uint
%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
%35 = OpTypeSampler
%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
%39 = OpTypeSampledImage %20
%_ptr_Input_v2float = OpTypePointer Input %v2float
%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
%uint_0 = OpConstant %uint 0
%bool = OpTypeBool
%_runtimearr_uint = OpTypeRuntimeArray %uint
%_struct_83 = OpTypeStruct %uint %_runtimearr_uint
%_ptr_StorageBuffer__struct_83 = OpTypePointer StorageBuffer %_struct_83
%85 = OpVariable %_ptr_StorageBuffer__struct_83 StorageBuffer
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%uint_10 = OpConstant %uint 10
%uint_4 = OpConstant %uint 4
%uint_1 = OpConstant %uint 1
%uint_23 = OpConstant %uint 23
%uint_2 = OpConstant %uint 2
%uint_9 = OpConstant %uint 9
%uint_3 = OpConstant %uint 3
%_ptr_Input_v4float = OpTypePointer Input %v4float
%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
%v4uint = OpTypeVector %uint 4
%uint_5 = OpConstant %uint 5
%uint_6 = OpConstant %uint 6
%uint_7 = OpConstant %uint 7
%uint_8 = OpConstant %uint 8
%131 = OpConstantNull %v4float
)";
const std::string defs_after =
R"(OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
OpExecutionMode %MainPs OriginUpperLeft
OpSource HLSL 500
OpName %MainPs "MainPs"
OpName %g_tColor "g_tColor"
OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
OpName %_ ""
OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 3
OpDecorate %g_tColor Binding 0
OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
OpDecorate %PerViewConstantBuffer_t Block
OpDecorate %g_sAniso DescriptorSet 0
OpDecorate %i_vTextureCoords Location 0
OpDecorate %_entryPointOutput_vColor Location 0
OpDecorate %10 DescriptorSet 7
OpDecorate %10 Binding 0
OpDecorate %gl_FragCoord BuiltIn FragCoord
OpDecorate %_runtimearr_uint ArrayStride 4
OpDecorate %_struct_34 Block
OpMemberDecorate %_struct_34 0 Offset 0
OpMemberDecorate %_struct_34 1 Offset 4
OpDecorate %74 DescriptorSet 7
OpDecorate %74 Binding 0
%void = OpTypeVoid
%12 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%v4float = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%18 = OpTypeImage %float 2D 0 0 0 1 Unknown
%uint = OpTypeInt 32 0
%uint_128 = OpConstant %uint 128
%_arr_18_uint_128 = OpTypeArray %18 %uint_128
%_ptr_UniformConstant__arr_18_uint_128 = OpTypePointer UniformConstant %_arr_18_uint_128
%g_tColor = OpVariable %_ptr_UniformConstant__arr_18_uint_128 UniformConstant
%PerViewConstantBuffer_t = OpTypeStruct %uint
%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
%_ptr_UniformConstant_18 = OpTypePointer UniformConstant %18
%26 = OpTypeSampler
%_ptr_UniformConstant_26 = OpTypePointer UniformConstant %26
%g_sAniso = OpVariable %_ptr_UniformConstant_26 UniformConstant
%28 = OpTypeSampledImage %18
%_ptr_Input_v2float = OpTypePointer Input %v2float
%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
%uint_0 = OpConstant %uint 0
%bool = OpTypeBool
%_runtimearr_uint = OpTypeRuntimeArray %uint
%_struct_34 = OpTypeStruct %uint %_runtimearr_uint
%_ptr_StorageBuffer__struct_34 = OpTypePointer StorageBuffer %_struct_34
%10 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%uint_10 = OpConstant %uint 10
%uint_4 = OpConstant %uint 4
%uint_1 = OpConstant %uint 1
%uint_23 = OpConstant %uint 23
%uint_2 = OpConstant %uint 2
%uint_9 = OpConstant %uint 9
%uint_3 = OpConstant %uint 3
%_ptr_Input_v4float = OpTypePointer Input %v4float
%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
%v4uint = OpTypeVector %uint 4
%uint_5 = OpConstant %uint 5
%uint_6 = OpConstant %uint 6
%uint_7 = OpConstant %uint 7
%uint_8 = OpConstant %uint 8
%50 = OpConstantNull %v4float
%68 = OpTypeFunction %void %uint %uint %uint %uint
%74 = OpVariable %_ptr_StorageBuffer__struct_34 StorageBuffer
%uint_82 = OpConstant %uint 82
)";
const std::string func_before =
R"(%MainPs = OpFunction %void None %3
%5 = OpLabel
%53 = OpLoad %v2float %i_vTextureCoords
%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
%64 = OpLoad %uint %63
%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
%67 = OpLoad %35 %g_sAniso
%78 = OpLoad %20 %65
%79 = OpSampledImage %39 %78 %67
%71 = OpImageSampleImplicitLod %v4float %79 %53
OpStore %_entryPointOutput_vColor %71
OpReturn
OpFunctionEnd
)";
const std::string func_after =
R"(%MainPs = OpFunction %void None %12
%51 = OpLabel
%52 = OpLoad %v2float %i_vTextureCoords
%53 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
%54 = OpLoad %uint %53
%55 = OpAccessChain %_ptr_UniformConstant_18 %g_tColor %54
%56 = OpLoad %26 %g_sAniso
%57 = OpLoad %18 %55
%58 = OpSampledImage %28 %57 %56
%60 = OpULessThan %bool %54 %uint_128
OpSelectionMerge %61 None
OpBranchConditional %60 %62 %63
%62 = OpLabel
%64 = OpLoad %18 %55
%65 = OpSampledImage %28 %64 %56
%66 = OpImageSampleImplicitLod %v4float %65 %52
OpBranch %61
%63 = OpLabel
%105 = OpFunctionCall %void %67 %uint_82 %uint_0 %54 %uint_128
OpBranch %61
%61 = OpLabel
%106 = OpPhi %v4float %66 %62 %50 %63
OpStore %_entryPointOutput_vColor %106
OpReturn
OpFunctionEnd
)";
const std::string output_func =
R"(%67 = OpFunction %void None %68
%69 = OpFunctionParameter %uint
%70 = OpFunctionParameter %uint
%71 = OpFunctionParameter %uint
%72 = OpFunctionParameter %uint
%73 = OpLabel
%75 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_0
%76 = OpAtomicIAdd %uint %75 %uint_4 %uint_0 %uint_9
%77 = OpIAdd %uint %76 %uint_9
%78 = OpArrayLength %uint %74 1
%79 = OpULessThanEqual %bool %77 %78
OpSelectionMerge %80 None
OpBranchConditional %79 %81 %80
%81 = OpLabel
%82 = OpIAdd %uint %76 %uint_0
%83 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %82
OpStore %83 %uint_9
%84 = OpIAdd %uint %76 %uint_1
%85 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %84
OpStore %85 %uint_23
%86 = OpIAdd %uint %76 %uint_2
%87 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %86
OpStore %87 %69
%88 = OpIAdd %uint %76 %uint_3
%89 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %88
OpStore %89 %uint_4
%90 = OpLoad %v4float %gl_FragCoord
%91 = OpBitcast %v4uint %90
%92 = OpCompositeExtract %uint %91 0
%93 = OpIAdd %uint %76 %uint_4
%94 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %93
OpStore %94 %92
%95 = OpCompositeExtract %uint %91 1
%96 = OpIAdd %uint %76 %uint_5
%97 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %96
OpStore %97 %95
%98 = OpIAdd %uint %76 %uint_6
%99 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %98
OpStore %99 %70
%100 = OpIAdd %uint %76 %uint_7
%101 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %100
OpStore %101 %71
%102 = OpIAdd %uint %76 %uint_8
%103 = OpAccessChain %_ptr_StorageBuffer_uint %74 %uint_1 %102
OpStore %103 %72
OpBranch %80
%80 = OpLabel
OpReturn
OpFunctionEnd
)";
// SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndCheck<InstBindlessCheckPass>(
defs_before + func_before, defs_after + func_after + output_func, true,
true);
}
TEST_F(InstBindlessTest, InstrumentOpImage) {
// This test verifies that the pass will correctly instrument shader
// using OpImage. This test was created by editing the SPIR-V
@ -1848,6 +1580,261 @@ OpFunctionEnd
true);
}
TEST_F(InstBindlessTest, RuntimeArray) {
// This test verifies that the pass will correctly instrument shader
// with runtime descriptor array. This test was created by editing the
// SPIR-V from the Simple test.
const std::string defs_before =
R"(OpCapability Shader
OpCapability RuntimeDescriptorArrayEXT
OpExtension "SPV_EXT_descriptor_indexing"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor
OpExecutionMode %MainPs OriginUpperLeft
OpSource HLSL 500
OpName %MainPs "MainPs"
OpName %g_tColor "g_tColor"
OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
OpName %_ ""
OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 0
OpDecorate %g_tColor Binding 0
OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
OpDecorate %PerViewConstantBuffer_t Block
OpDecorate %g_sAniso DescriptorSet 0
OpDecorate %g_sAniso Binding 1
OpDecorate %i_vTextureCoords Location 0
OpDecorate %_entryPointOutput_vColor Location 0
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%v4float = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%20 = OpTypeImage %float 2D 0 0 0 1 Unknown
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%_rarr_20 = OpTypeRuntimeArray %20
%_ptr_UniformConstant__arr_20 = OpTypePointer UniformConstant %_rarr_20
%g_tColor = OpVariable %_ptr_UniformConstant__arr_20 UniformConstant
%PerViewConstantBuffer_t = OpTypeStruct %uint
%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
%_ptr_UniformConstant_20 = OpTypePointer UniformConstant %20
%35 = OpTypeSampler
%_ptr_UniformConstant_35 = OpTypePointer UniformConstant %35
%g_sAniso = OpVariable %_ptr_UniformConstant_35 UniformConstant
%39 = OpTypeSampledImage %20
%_ptr_Input_v2float = OpTypePointer Input %v2float
%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
)";
const std::string defs_after =
R"(OpCapability Shader
OpCapability RuntimeDescriptorArrayEXT
OpExtension "SPV_EXT_descriptor_indexing"
OpExtension "SPV_KHR_storage_buffer_storage_class"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %MainPs "MainPs" %i_vTextureCoords %_entryPointOutput_vColor %gl_FragCoord
OpExecutionMode %MainPs OriginUpperLeft
OpSource HLSL 500
OpName %MainPs "MainPs"
OpName %g_tColor "g_tColor"
OpName %PerViewConstantBuffer_t "PerViewConstantBuffer_t"
OpMemberName %PerViewConstantBuffer_t 0 "g_nDataIdx"
OpName %_ ""
OpName %g_sAniso "g_sAniso"
OpName %i_vTextureCoords "i.vTextureCoords"
OpName %_entryPointOutput_vColor "@entryPointOutput.vColor"
OpDecorate %g_tColor DescriptorSet 0
OpDecorate %g_tColor Binding 0
OpMemberDecorate %PerViewConstantBuffer_t 0 Offset 0
OpDecorate %PerViewConstantBuffer_t Block
OpDecorate %g_sAniso DescriptorSet 0
OpDecorate %g_sAniso Binding 1
OpDecorate %i_vTextureCoords Location 0
OpDecorate %_entryPointOutput_vColor Location 0
OpDecorate %_runtimearr_uint ArrayStride 4
OpDecorate %_struct_40 Block
OpMemberDecorate %_struct_40 0 Offset 0
OpDecorate %42 DescriptorSet 7
OpDecorate %42 Binding 1
OpDecorate %_struct_64 Block
OpMemberDecorate %_struct_64 0 Offset 0
OpMemberDecorate %_struct_64 1 Offset 4
OpDecorate %66 DescriptorSet 7
OpDecorate %66 Binding 0
OpDecorate %gl_FragCoord BuiltIn FragCoord
%void = OpTypeVoid
%10 = OpTypeFunction %void
%float = OpTypeFloat 32
%v2float = OpTypeVector %float 2
%v4float = OpTypeVector %float 4
%int = OpTypeInt 32 1
%int_0 = OpConstant %int 0
%16 = OpTypeImage %float 2D 0 0 0 1 Unknown
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%_runtimearr_16 = OpTypeRuntimeArray %16
%_ptr_UniformConstant__runtimearr_16 = OpTypePointer UniformConstant %_runtimearr_16
%g_tColor = OpVariable %_ptr_UniformConstant__runtimearr_16 UniformConstant
%PerViewConstantBuffer_t = OpTypeStruct %uint
%_ptr_PushConstant_PerViewConstantBuffer_t = OpTypePointer PushConstant %PerViewConstantBuffer_t
%_ = OpVariable %_ptr_PushConstant_PerViewConstantBuffer_t PushConstant
%_ptr_PushConstant_uint = OpTypePointer PushConstant %uint
%_ptr_UniformConstant_16 = OpTypePointer UniformConstant %16
%24 = OpTypeSampler
%_ptr_UniformConstant_24 = OpTypePointer UniformConstant %24
%g_sAniso = OpVariable %_ptr_UniformConstant_24 UniformConstant
%26 = OpTypeSampledImage %16
%_ptr_Input_v2float = OpTypePointer Input %v2float
%i_vTextureCoords = OpVariable %_ptr_Input_v2float Input
%_ptr_Output_v4float = OpTypePointer Output %v4float
%_entryPointOutput_vColor = OpVariable %_ptr_Output_v4float Output
%uint_0 = OpConstant %uint 0
%_runtimearr_uint = OpTypeRuntimeArray %uint
%_struct_40 = OpTypeStruct %_runtimearr_uint
%_ptr_StorageBuffer__struct_40 = OpTypePointer StorageBuffer %_struct_40
%42 = OpVariable %_ptr_StorageBuffer__struct_40 StorageBuffer
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
%bool = OpTypeBool
%58 = OpTypeFunction %void %uint %uint %uint %uint
%_struct_64 = OpTypeStruct %uint %_runtimearr_uint
%_ptr_StorageBuffer__struct_64 = OpTypePointer StorageBuffer %_struct_64
%66 = OpVariable %_ptr_StorageBuffer__struct_64 StorageBuffer
%uint_9 = OpConstant %uint 9
%uint_4 = OpConstant %uint 4
%uint_23 = OpConstant %uint 23
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%_ptr_Input_v4float = OpTypePointer Input %v4float
%gl_FragCoord = OpVariable %_ptr_Input_v4float Input
%v4uint = OpTypeVector %uint 4
%uint_5 = OpConstant %uint 5
%uint_6 = OpConstant %uint 6
%uint_7 = OpConstant %uint 7
%uint_8 = OpConstant %uint 8
%uint_59 = OpConstant %uint 59
%110 = OpConstantNull %v4float
)";
const std::string func_before =
R"(%MainPs = OpFunction %void None %3
%5 = OpLabel
%53 = OpLoad %v2float %i_vTextureCoords
%63 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
%64 = OpLoad %uint %63
%65 = OpAccessChain %_ptr_UniformConstant_20 %g_tColor %64
%66 = OpLoad %20 %65
%67 = OpLoad %35 %g_sAniso
%68 = OpSampledImage %39 %66 %67
%71 = OpImageSampleImplicitLod %v4float %68 %53
OpStore %_entryPointOutput_vColor %71
OpReturn
OpFunctionEnd
)";
const std::string func_after =
R"(%MainPs = OpFunction %void None %10
%29 = OpLabel
%30 = OpLoad %v2float %i_vTextureCoords
%31 = OpAccessChain %_ptr_PushConstant_uint %_ %int_0
%32 = OpLoad %uint %31
%33 = OpAccessChain %_ptr_UniformConstant_16 %g_tColor %32
%34 = OpLoad %16 %33
%35 = OpLoad %24 %g_sAniso
%36 = OpSampledImage %26 %34 %35
%44 = OpAccessChain %_ptr_StorageBuffer_uint %42 %uint_0 %uint_1
%45 = OpLoad %uint %44
%46 = OpIAdd %uint %45 %uint_0
%47 = OpAccessChain %_ptr_StorageBuffer_uint %42 %uint_0 %46
%48 = OpLoad %uint %47
%50 = OpULessThan %bool %32 %48
OpSelectionMerge %51 None
OpBranchConditional %50 %52 %53
%52 = OpLabel
%54 = OpLoad %16 %33
%55 = OpSampledImage %26 %54 %35
%56 = OpImageSampleImplicitLod %v4float %55 %30
OpBranch %51
%53 = OpLabel
%109 = OpFunctionCall %void %57 %uint_59 %uint_0 %32 %48
OpBranch %51
%51 = OpLabel
%111 = OpPhi %v4float %56 %52 %110 %53
OpStore %_entryPointOutput_vColor %111
OpReturn
OpFunctionEnd
)";
const std::string output_func =
R"(%57 = OpFunction %void None %58
%59 = OpFunctionParameter %uint
%60 = OpFunctionParameter %uint
%61 = OpFunctionParameter %uint
%62 = OpFunctionParameter %uint
%63 = OpLabel
%67 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_0
%70 = OpAtomicIAdd %uint %67 %uint_4 %uint_0 %uint_9
%71 = OpIAdd %uint %70 %uint_9
%72 = OpArrayLength %uint %66 1
%73 = OpULessThanEqual %bool %71 %72
OpSelectionMerge %74 None
OpBranchConditional %73 %75 %74
%75 = OpLabel
%76 = OpIAdd %uint %70 %uint_0
%77 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %76
OpStore %77 %uint_9
%79 = OpIAdd %uint %70 %uint_1
%80 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %79
OpStore %80 %uint_23
%82 = OpIAdd %uint %70 %uint_2
%83 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %82
OpStore %83 %59
%85 = OpIAdd %uint %70 %uint_3
%86 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %85
OpStore %86 %uint_4
%89 = OpLoad %v4float %gl_FragCoord
%91 = OpBitcast %v4uint %89
%92 = OpCompositeExtract %uint %91 0
%93 = OpIAdd %uint %70 %uint_4
%94 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %93
OpStore %94 %92
%95 = OpCompositeExtract %uint %91 1
%97 = OpIAdd %uint %70 %uint_5
%98 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %97
OpStore %98 %95
%100 = OpIAdd %uint %70 %uint_6
%101 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %100
OpStore %101 %60
%103 = OpIAdd %uint %70 %uint_7
%104 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %103
OpStore %104 %61
%106 = OpIAdd %uint %70 %uint_8
%107 = OpAccessChain %_ptr_StorageBuffer_uint %66 %uint_1 %106
OpStore %107 %62
OpBranch %74
%74 = OpLabel
OpReturn
OpFunctionEnd
)";
// SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SinglePassRunAndCheck<InstBindlessCheckPass>(
defs_before + func_before, defs_after + func_after + output_func, true,
true);
}
// TODO(greg-lunarg): Add tests to verify handling of these cases:
//
// TODO(greg-lunarg): Come up with cases to put here :)

View File

@ -222,6 +222,133 @@ TEST(Optimizer, CanRegisterPassesFromFlags) {
EXPECT_EQ(msg_level, SPV_MSG_ERROR);
}
TEST(Optimizer, WebGPUModeSetsCorrectPasses) {
Optimizer opt(SPV_ENV_WEBGPU_0);
opt.RegisterWebGPUPasses();
std::vector<const char*> pass_names = opt.GetPassNames();
std::vector<std::string> registered_passes;
for (auto name = pass_names.begin(); name != pass_names.end(); ++name)
registered_passes.push_back(*name);
std::vector<std::string> expected_passes = {
"eliminate-dead-branches", "eliminate-dead-code-aggressive",
"flatten-decorations", "strip-debug"};
std::sort(registered_passes.begin(), registered_passes.end());
std::sort(expected_passes.begin(), expected_passes.end());
ASSERT_EQ(registered_passes.size(), expected_passes.size());
for (size_t i = 0; i < registered_passes.size(); i++)
EXPECT_EQ(registered_passes[i], expected_passes[i]);
}
TEST(Optimizer, WebGPUModeFlattenDecorationsRuns) {
const std::string input = R"(OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint Fragment %main "main" %hue %saturation %value
OpExecutionMode %main OriginUpperLeft
OpDecorate %group Flat
OpDecorate %group NoPerspective
%group = OpDecorationGroup
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%float = OpTypeFloat 32
%_ptr_Input_float = OpTypePointer Input %float
%hue = OpVariable %_ptr_Input_float Input
%saturation = OpVariable %_ptr_Input_float Input
%value = OpVariable %_ptr_Input_float Input
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string expected = R"(OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint Fragment %1 "main" %2 %3 %4
OpExecutionMode %1 OriginUpperLeft
%void = OpTypeVoid
%7 = OpTypeFunction %void
%float = OpTypeFloat 32
%_ptr_Input_float = OpTypePointer Input %float
%2 = OpVariable %_ptr_Input_float Input
%3 = OpVariable %_ptr_Input_float Input
%4 = OpVariable %_ptr_Input_float Input
%1 = OpFunction %void None %7
%10 = OpLabel
OpReturn
OpFunctionEnd
)";
SpirvTools tools(SPV_ENV_WEBGPU_0);
std::vector<uint32_t> binary;
tools.Assemble(input, &binary);
Optimizer opt(SPV_ENV_WEBGPU_0);
opt.RegisterWebGPUPasses();
std::vector<uint32_t> optimized;
ValidatorOptions validator_options;
ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
validator_options, true));
std::string disassembly;
tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
EXPECT_EQ(expected, disassembly);
}
TEST(Optimizer, WebGPUModeStripDebugRuns) {
const std::string input = R"(OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint Vertex %func "shader"
OpName %main "main"
OpName %void_fn "void_fn"
%void = OpTypeVoid
%void_f = OpTypeFunction %void
%func = OpFunction %void None %void_f
%label = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string expected = R"(OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint Vertex %1 "shader"
%void = OpTypeVoid
%5 = OpTypeFunction %void
%1 = OpFunction %void None %5
%6 = OpLabel
OpReturn
OpFunctionEnd
)";
SpirvTools tools(SPV_ENV_WEBGPU_0);
std::vector<uint32_t> binary;
tools.Assemble(input, &binary);
Optimizer opt(SPV_ENV_WEBGPU_0);
opt.RegisterWebGPUPasses();
std::vector<uint32_t> optimized;
ValidatorOptions validator_options;
ASSERT_TRUE(opt.Run(binary.data(), binary.size(), &optimized,
validator_options, true));
std::string disassembly;
tools.Disassemble(optimized.data(), optimized.size(), &disassembly);
EXPECT_EQ(expected, disassembly);
}
} // namespace
} // namespace opt
} // namespace spvtools

View File

@ -20,6 +20,7 @@ add_spvtools_unittest(TARGET reduce
reduce_test_util.cpp
reduce_test_util.h
reducer_test.cpp
remove_function_test.cpp
remove_opname_instruction_test.cpp
remove_unreferenced_instruction_test.cpp
structured_loop_to_selection_test.cpp

View File

@ -0,0 +1,294 @@
// 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 "reduce_test_util.h"
#include "source/opt/build_module.h"
#include "source/reduce/reduction_opportunity.h"
#include "source/reduce/remove_function_reduction_opportunity_finder.h"
namespace spvtools {
namespace reduce {
namespace {
// Helper to count the number of functions in the module.
// Remove if there turns out to be a more direct way to do this.
uint32_t count_functions(opt::IRContext* context) {
uint32_t result = 0;
for (auto& function : *context->module()) {
(void)(function);
++result;
}
return result;
}
TEST(RemoveFunctionTest, BasicCheck) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
%6 = OpFunction %2 None %3
%7 = OpLabel
OpReturn
OpFunctionEnd
%8 = OpFunction %2 None %3
%9 = OpLabel
%10 = OpFunctionCall %2 %6
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
ASSERT_EQ(3, count_functions(context.get()));
auto ops =
RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
ASSERT_EQ(2, count_functions(context.get()));
std::string after_first = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
%6 = OpFunction %2 None %3
%7 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(env, after_first, context.get());
ops = RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(1, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
ASSERT_EQ(1, count_functions(context.get()));
std::string after_second = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(env, after_second, context.get());
}
TEST(RemoveFunctionTest, NothingToRemove) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
%11 = OpFunctionCall %2 %8
OpReturn
OpFunctionEnd
%6 = OpFunction %2 None %3
%7 = OpLabel
OpReturn
OpFunctionEnd
%8 = OpFunction %2 None %3
%9 = OpLabel
%10 = OpFunctionCall %2 %6
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
auto ops =
RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(0, ops.size());
}
TEST(RemoveFunctionTest, TwoRemovableFunctions) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
%6 = OpFunction %2 None %3
%7 = OpLabel
OpReturn
OpFunctionEnd
%8 = OpFunction %2 None %3
%9 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
ASSERT_EQ(3, count_functions(context.get()));
auto ops =
RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(2, ops.size());
ASSERT_TRUE(ops[0]->PreconditionHolds());
ops[0]->TryToApply();
ASSERT_EQ(2, count_functions(context.get()));
ASSERT_TRUE(ops[1]->PreconditionHolds());
ops[1]->TryToApply();
ASSERT_EQ(1, count_functions(context.get()));
std::string after = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
)";
CheckEqual(env, after, context.get());
}
TEST(RemoveFunctionTest, NoRemovalsDueToOpName) {
std::string shader = R"(
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %4 "main"
OpExecutionMode %4 OriginUpperLeft
OpSource ESSL 310
OpName %4 "main"
OpName %6 "foo("
OpName %8 "bar("
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%4 = OpFunction %2 None %3
%5 = OpLabel
OpReturn
OpFunctionEnd
%6 = OpFunction %2 None %3
%7 = OpLabel
OpReturn
OpFunctionEnd
%8 = OpFunction %2 None %3
%9 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
auto ops =
RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(0, ops.size());
}
TEST(RemoveFunctionTest, NoRemovalDueToLinkageDecoration) {
// The non-entry point function is not removable because it is referenced by a
// linkage decoration. Thus no function can be removed.
std::string shader = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %1 "main"
OpName %1 "main"
OpDecorate %2 LinkageAttributes "ExportedFunc" Export
%4 = OpTypeVoid
%5 = OpTypeFunction %4
%1 = OpFunction %4 None %5
%6 = OpLabel
OpReturn
OpFunctionEnd
%2 = OpFunction %4 None %5
%7 = OpLabel
OpReturn
OpFunctionEnd
)";
const auto env = SPV_ENV_UNIVERSAL_1_3;
const auto consumer = nullptr;
const auto context =
BuildModule(env, consumer, shader, kReduceAssembleOption);
auto ops =
RemoveFunctionReductionOpportunityFinder().GetAvailableOpportunities(
context.get());
ASSERT_EQ(0, ops.size());
}
} // namespace
} // namespace reduce
} // namespace spvtools

View File

@ -51,8 +51,9 @@ add_spvtools_unittest(TARGET val_limits
PCH_FILE pch_test_val
)
add_spvtools_unittest(TARGET val_ijklmnop
add_spvtools_unittest(TARGET val_fghijklmnop
SRCS
val_function_test.cpp
val_id_test.cpp
val_image_test.cpp
val_interfaces_test.cpp

View File

@ -1995,7 +1995,7 @@ INSTANTIATE_TEST_SUITE_P(
"needs to be a 4-component 32-bit float vector",
"is not a float vector"))));
INSTANTIATE_TEST_CASE_P(
INSTANTIATE_TEST_SUITE_P(
PositionArrayedF32Vec4Vertex, ValidateWebGPUCombineBuiltInArrayedVariable,
Combine(Values("Position"), Values("Vertex"), Values("Output"),
Values("%f32vec4"),

View File

@ -5517,6 +5517,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
return std::string(R"(
OpCapability Shader
OpCapability RuntimeDescriptorArrayEXT
OpExtension "SPV_EXT_descriptor_indexing"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main"
@ -5546,6 +5548,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
%void_fn = OpTypeFunction %void
%float = OpTypeFloat 32
%float_0 = OpConstant %float 0
%int = OpTypeInt 32 0
%int_2 = OpConstant %int 2
%struct_b = OpTypeStruct %float
%struct_bb = OpTypeStruct %float
%rtarr = OpTypeRuntimeArray %float
@ -5555,6 +5559,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
%imstor = OpTypeImage %float 2D 0 0 0 2 R32f
; sampled image
%imsam = OpTypeImage %float 2D 0 0 0 1 R32f
%array_imstor = OpTypeArray %imstor %int_2
%rta_imstor = OpTypeRuntimeArray %imstor
%_ptr_Uniform_stb = OpTypePointer Uniform %struct_b
%_ptr_Uniform_stbb = OpTypePointer Uniform %struct_bb
@ -5565,6 +5571,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
%_ptr_Function = OpTypePointer Function %float
%_ptr_imstor = OpTypePointer UniformConstant %imstor
%_ptr_imsam = OpTypePointer UniformConstant %imsam
%_ptr_array_imstor = OpTypePointer UniformConstant %array_imstor
%_ptr_rta_imstor = OpTypePointer UniformConstant %rta_imstor
%extra_fn = OpTypeFunction %void %float %_ptr_Private %_ptr_imstor
@ -5576,6 +5584,8 @@ std::string ShaderWithNonWritableTarget(const std::string& target,
%var_priv = OpVariable %_ptr_Private Private
%var_imstor = OpVariable %_ptr_imstor UniformConstant
%var_imsam = OpVariable %_ptr_imsam UniformConstant
%var_array_imstor = OpVariable %_ptr_array_imstor UniformConstant
%var_rta_imstor = OpVariable %_ptr_rta_imstor UniformConstant
%helper = OpFunction %void None %extra_fn
%param_f = OpFunctionParameter %float
@ -5754,6 +5764,20 @@ TEST_F(ValidateDecorations, NonWritableVarFunctionBad) {
"buffer\n %var_func"));
}
TEST_F(ValidateDecorations, NonWritableArrayGood) {
std::string spirv = ShaderWithNonWritableTarget("%var_array_imstor");
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_F(ValidateDecorations, NonWritableRuntimeArrayGood) {
std::string spirv = ShaderWithNonWritableTarget("%var_rta_imstor");
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}
TEST_P(ValidateWebGPUCombineDecorationResult, Decorate) {
const char* const decoration = std::get<0>(GetParam());
const TestResult& test_result = std::get<1>(GetParam());

View File

@ -125,6 +125,13 @@ void ValidateBase<T>::OverwriteAssembledBinary(uint32_t index, uint32_t word) {
template <typename T>
spv_result_t ValidateBase<T>::ValidateInstructions(spv_target_env env) {
DestroyDiagnostic();
if (binary_ == nullptr) {
fprintf(stderr,
"ERROR: Attempting to validate a null binary, did you forget to "
"call CompileSuccessfully?");
fflush(stderr);
}
assert(binary_ != nullptr);
return spvValidateWithOptions(ScopedContext(env).context, options_,
get_const_binary(), &diagnostic_);
}

View File

@ -0,0 +1,424 @@
// 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 <sstream>
#include <string>
#include <tuple>
#include "gmock/gmock.h"
#include "test/test_fixture.h"
#include "test/unit_spirv.h"
#include "test/val/val_fixtures.h"
namespace spvtools {
namespace val {
namespace {
using ::testing::Combine;
using ::testing::HasSubstr;
using ::testing::Values;
using ValidateFunctionCall = spvtest::ValidateBase<std::string>;
std::string GenerateShader(const std::string& storage_class,
const std::string& capabilities,
const std::string& extensions) {
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability AtomicStorage
)" + capabilities + R"(
OpExtension "SPV_KHR_storage_buffer_storage_class"
)" +
extensions + R"(
OpMemoryModel Logical GLSL450
OpName %var "var"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr = OpTypePointer )" + storage_class + R"( %int
%caller_ty = OpTypeFunction %void
%callee_ty = OpTypeFunction %void %ptr
)";
if (storage_class != "Function") {
spirv += "%var = OpVariable %ptr " + storage_class;
}
spirv += R"(
%caller = OpFunction %void None %caller_ty
%1 = OpLabel
)";
if (storage_class == "Function") {
spirv += "%var = OpVariable %ptr Function";
}
spirv += R"(
%call = OpFunctionCall %void %callee %var
OpReturn
OpFunctionEnd
%callee = OpFunction %void None %callee_ty
%param = OpFunctionParameter %ptr
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
return spirv;
}
std::string GenerateShaderParameter(const std::string& storage_class,
const std::string& capabilities,
const std::string& extensions) {
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability AtomicStorage
)" + capabilities + R"(
OpExtension "SPV_KHR_storage_buffer_storage_class"
)" +
extensions + R"(
OpMemoryModel Logical GLSL450
OpName %p "p"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%ptr = OpTypePointer )" + storage_class + R"( %int
%func_ty = OpTypeFunction %void %ptr
%caller = OpFunction %void None %func_ty
%p = OpFunctionParameter %ptr
%1 = OpLabel
%call = OpFunctionCall %void %callee %p
OpReturn
OpFunctionEnd
%callee = OpFunction %void None %func_ty
%param = OpFunctionParameter %ptr
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
return spirv;
}
std::string GenerateShaderAccessChain(const std::string& storage_class,
const std::string& capabilities,
const std::string& extensions) {
std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability AtomicStorage
)" + capabilities + R"(
OpExtension "SPV_KHR_storage_buffer_storage_class"
)" +
extensions + R"(
OpMemoryModel Logical GLSL450
OpName %var "var"
OpName %gep "gep"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int2 = OpTypeVector %int 2
%int_0 = OpConstant %int 0
%ptr = OpTypePointer )" + storage_class + R"( %int2
%ptr2 = OpTypePointer )" +
storage_class + R"( %int
%caller_ty = OpTypeFunction %void
%callee_ty = OpTypeFunction %void %ptr2
)";
if (storage_class != "Function") {
spirv += "%var = OpVariable %ptr " + storage_class;
}
spirv += R"(
%caller = OpFunction %void None %caller_ty
%1 = OpLabel
)";
if (storage_class == "Function") {
spirv += "%var = OpVariable %ptr Function";
}
spirv += R"(
%gep = OpAccessChain %ptr2 %var %int_0
%call = OpFunctionCall %void %callee %gep
OpReturn
OpFunctionEnd
%callee = OpFunction %void None %callee_ty
%param = OpFunctionParameter %ptr2
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
return spirv;
}
TEST_P(ValidateFunctionCall, VariableNoVariablePointers) {
const std::string storage_class = GetParam();
std::string spirv = GenerateShader(storage_class, "", "");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
bool valid =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
CompileSuccessfully(spirv);
if (valid) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
if (storage_class == "StorageBuffer") {
EXPECT_THAT(getDiagnosticString(),
HasSubstr("StorageBuffer pointer operand 1[%var] requires a "
"variable pointers capability"));
} else {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 1[%var]"));
}
}
}
TEST_P(ValidateFunctionCall, VariableVariablePointersStorageClass) {
const std::string storage_class = GetParam();
std::string spirv = GenerateShader(
storage_class, "OpCapability VariablePointersStorageBuffer",
"OpExtension \"SPV_KHR_variable_pointers\"");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private",
"Workgroup", "StorageBuffer", "AtomicCounter"};
bool valid =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
CompileSuccessfully(spirv);
if (valid) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 1[%var]"));
}
}
TEST_P(ValidateFunctionCall, VariableVariablePointers) {
const std::string storage_class = GetParam();
std::string spirv =
GenerateShader(storage_class, "OpCapability VariablePointers",
"OpExtension \"SPV_KHR_variable_pointers\"");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private",
"Workgroup", "StorageBuffer", "AtomicCounter"};
bool valid =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
CompileSuccessfully(spirv);
if (valid) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 1[%var]"));
}
}
TEST_P(ValidateFunctionCall, ParameterNoVariablePointers) {
const std::string storage_class = GetParam();
std::string spirv = GenerateShaderParameter(storage_class, "", "");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
bool valid =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
CompileSuccessfully(spirv);
if (valid) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
if (storage_class == "StorageBuffer") {
EXPECT_THAT(getDiagnosticString(),
HasSubstr("StorageBuffer pointer operand 1[%p] requires a "
"variable pointers capability"));
} else {
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 1[%p]"));
}
}
}
TEST_P(ValidateFunctionCall, ParameterVariablePointersStorageBuffer) {
const std::string storage_class = GetParam();
std::string spirv = GenerateShaderParameter(
storage_class, "OpCapability VariablePointersStorageBuffer",
"OpExtension \"SPV_KHR_variable_pointers\"");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private",
"Workgroup", "StorageBuffer", "AtomicCounter"};
bool valid =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
CompileSuccessfully(spirv);
if (valid) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 1[%p]"));
}
}
TEST_P(ValidateFunctionCall, ParameterVariablePointers) {
const std::string storage_class = GetParam();
std::string spirv =
GenerateShaderParameter(storage_class, "OpCapability VariablePointers",
"OpExtension \"SPV_KHR_variable_pointers\"");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private",
"Workgroup", "StorageBuffer", "AtomicCounter"};
bool valid =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
CompileSuccessfully(spirv);
if (valid) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
EXPECT_THAT(getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 1[%p]"));
}
}
TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationNoVariablePointers) {
const std::string storage_class = GetParam();
std::string spirv = GenerateShaderAccessChain(storage_class, "", "");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private", "Workgroup", "AtomicCounter"};
bool valid_sc =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
CompileSuccessfully(spirv);
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
if (valid_sc) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Pointer operand 2[%gep] must be a memory object declaration"));
} else {
if (storage_class == "StorageBuffer") {
EXPECT_THAT(getDiagnosticString(),
HasSubstr("StorageBuffer pointer operand 2[%gep] requires a "
"variable pointers capability"));
} else {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
}
}
}
TEST_P(ValidateFunctionCall,
NonMemoryObjectDeclarationVariablePointersStorageBuffer) {
const std::string storage_class = GetParam();
std::string spirv = GenerateShaderAccessChain(
storage_class, "OpCapability VariablePointersStorageBuffer",
"OpExtension \"SPV_KHR_variable_pointers\"");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private",
"Workgroup", "StorageBuffer", "AtomicCounter"};
bool valid_sc =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
bool validate = storage_class == "StorageBuffer";
CompileSuccessfully(spirv);
if (validate) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
if (valid_sc) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Pointer operand 2[%gep] must be a memory object declaration"));
} else {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
}
}
}
TEST_P(ValidateFunctionCall, NonMemoryObjectDeclarationVariablePointers) {
const std::string storage_class = GetParam();
std::string spirv =
GenerateShaderAccessChain(storage_class, "OpCapability VariablePointers",
"OpExtension \"SPV_KHR_variable_pointers\"");
const std::vector<std::string> valid_storage_classes = {
"UniformConstant", "Function", "Private",
"Workgroup", "StorageBuffer", "AtomicCounter"};
bool valid_sc =
std::find(valid_storage_classes.begin(), valid_storage_classes.end(),
storage_class) != valid_storage_classes.end();
bool validate =
storage_class == "StorageBuffer" || storage_class == "Workgroup";
CompileSuccessfully(spirv);
if (validate) {
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
} else {
EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
if (valid_sc) {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr(
"Pointer operand 2[%gep] must be a memory object declaration"));
} else {
EXPECT_THAT(
getDiagnosticString(),
HasSubstr("Invalid storage class for pointer operand 2[%gep]"));
}
}
}
INSTANTIATE_TEST_SUITE_P(StorageClass, ValidateFunctionCall,
Values("UniformConstant", "Input", "Uniform", "Output",
"Workgroup", "Private", "Function",
"PushConstant", "Image", "StorageBuffer",
"AtomicCounter"));
} // namespace
} // namespace val
} // namespace spvtools

View File

@ -2274,6 +2274,7 @@ void createVariablePointerSpirvProgram(std::ostringstream* spirv,
*spirv << "OpCapability VariablePointers ";
*spirv << "OpExtension \"SPV_KHR_variable_pointers\" ";
}
*spirv << "OpExtension \"SPV_KHR_storage_buffer_storage_class\" ";
*spirv << R"(
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
@ -2282,12 +2283,12 @@ void createVariablePointerSpirvProgram(std::ostringstream* spirv,
%bool = OpTypeBool
%i32 = OpTypeInt 32 1
%f32 = OpTypeFloat 32
%f32ptr = OpTypePointer Uniform %f32
%f32ptr = OpTypePointer StorageBuffer %f32
%i = OpConstant %i32 1
%zero = OpConstant %i32 0
%float_1 = OpConstant %f32 1.0
%ptr1 = OpVariable %f32ptr Uniform
%ptr2 = OpVariable %f32ptr Uniform
%ptr1 = OpVariable %f32ptr StorageBuffer
%ptr2 = OpVariable %f32ptr StorageBuffer
)";
if (add_helper_function) {
*spirv << R"(

View File

@ -42,7 +42,9 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
add_spvtools_tool(TARGET spirv-dis SRCS dis/dis.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-val SRCS val/val.cpp util/cli_consumer.cpp LIBS ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-opt SRCS opt/opt.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-opt ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
if (NOT DEFINED IOS_PLATFORM) # iOS does not allow std::system calls which spirv-reduce requires
add_spvtools_tool(TARGET spirv-reduce SRCS reduce/reduce.cpp util/cli_consumer.cpp LIBS SPIRV-Tools-reduce ${SPIRV_TOOLS})
endif()
add_spvtools_tool(TARGET spirv-link SRCS link/linker.cpp LIBS SPIRV-Tools-link ${SPIRV_TOOLS})
add_spvtools_tool(TARGET spirv-stats
SRCS stats/stats.cpp
@ -62,7 +64,10 @@ if (NOT ${SPIRV_SKIP_EXECUTABLES})
${SPIRV_HEADER_INCLUDE_DIR})
set(SPIRV_INSTALL_TARGETS spirv-as spirv-dis spirv-val spirv-opt spirv-stats
spirv-cfg spirv-link spirv-reduce)
spirv-cfg spirv-link)
if(NOT DEFINED IOS_PLATFORM)
set(SPIRV_INSTALL_TARGETS ${SPIRV_INSTALL_TARGETS} spirv-reduce)
endif()
if(SPIRV_BUILD_COMPRESSION)
add_spvtools_tool(TARGET spirv-markv

View File

@ -25,6 +25,7 @@
#include "source/reduce/operand_to_dominating_id_reduction_opportunity_finder.h"
#include "source/reduce/operand_to_undef_reduction_opportunity_finder.h"
#include "source/reduce/reducer.h"
#include "source/reduce/remove_function_reduction_opportunity_finder.h"
#include "source/reduce/remove_opname_instruction_reduction_opportunity_finder.h"
#include "source/reduce/remove_unreferenced_instruction_reduction_opportunity_finder.h"
#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
@ -243,6 +244,8 @@ int main(int argc, const char** argv) {
StructuredLoopToSelectionReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<MergeBlocksReductionOpportunityFinder>());
reducer.AddReductionPass(
spvtools::MakeUnique<RemoveFunctionReductionOpportunityFinder>());
reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);