spirv-opt: add pass to Spread Volatile semantics (#4667)

Add a pass to spread Volatile semantics to variables with SMIDNV,
WarpIDNV, SubgroupSize, SubgroupLocalInvocationId, SubgroupEqMask,
SubgroupGeMask, SubgroupGtMask, SubgroupLeMask, or SubgroupLtMask BuiltIn
decorations or OpLoad for them when the shader model is the ray
generation, closest hit, miss, intersection, or callable shaders. This
pass can be used for VUID-StandaloneSpirv-VulkanMemoryModel-04678 and
VUID-StandaloneSpirv-VulkanMemoryModel-04679 (See "Standalone SPIR-V
Validation" section of Vulkan spec "Appendix A: Vulkan Environment for
SPIR-V").

Handle variables used by multiple entry points:

1. Update error check to make it working regardless of the order of
   entry points.
2. For a variable, if it is used by two entry points E1 and E2 and
   it needs the Volatile semantics for E1 while it does not for E2
  - If VulkanMemoryModel capability is enabled, which means we have to
    set memory operation of load instructions for the variable, we
    update load instructions in E1, but do not update the ones in E2.
  - If VulkanMemoryModel capability is disabled, which means we have
    to add Volatile decoration for the variable, we report an error
    because E1 needs to add Volatile decoration for the variable while
    E2 does not.

For the simplicity of the implementation, we assume that all functions
other than entry point functions are inlined.
This commit is contained in:
Jaebaek Seo 2022-01-25 13:14:36 -05:00 коммит произвёл GitHub
Родитель 6938af7f82
Коммит fb9a10cd48
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
12 изменённых файлов: 1580 добавлений и 1 удалений

Просмотреть файл

@ -165,6 +165,7 @@ SPVTOOLS_OPT_SRC_FILES := \
source/opt/scalar_replacement_pass.cpp \
source/opt/set_spec_constant_default_value_pass.cpp \
source/opt/simplification_pass.cpp \
source/opt/spread_volatile_semantics.cpp \
source/opt/ssa_rewrite_pass.cpp \
source/opt/strength_reduction_pass.cpp \
source/opt/strip_debug_info_pass.cpp \

Просмотреть файл

@ -732,6 +732,8 @@ static_library("spvtools_opt") {
"source/opt/set_spec_constant_default_value_pass.h",
"source/opt/simplification_pass.cpp",
"source/opt/simplification_pass.h",
"source/opt/spread_volatile_semantics.cpp",
"source/opt/spread_volatile_semantics.h",
"source/opt/ssa_rewrite_pass.cpp",
"source/opt/ssa_rewrite_pass.h",
"source/opt/strength_reduction_pass.cpp",

Просмотреть файл

@ -835,6 +835,19 @@ Optimizer::PassToken CreateFixStorageClassPass();
// inclusive.
Optimizer::PassToken CreateGraphicsRobustAccessPass();
// Create a pass to spread Volatile semantics to variables with SMIDNV,
// WarpIDNV, SubgroupSize, SubgroupLocalInvocationId, SubgroupEqMask,
// SubgroupGeMask, SubgroupGtMask, SubgroupLeMask, or SubgroupLtMask BuiltIn
// decorations or OpLoad for them when the shader model is the ray generation,
// closest hit, miss, intersection, or callable. This pass can be used for
// VUID-StandaloneSpirv-VulkanMemoryModel-04678 and
// VUID-StandaloneSpirv-VulkanMemoryModel-04679 (See "Standalone SPIR-V
// Validation" section of Vulkan spec "Appendix A: Vulkan Environment for
// SPIR-V"). When the SPIR-V version is 1.6 or above, the pass also spreads
// the Volatile semantics to a variable with HelperInvocation BuiltIn decoration
// in the fragement shader.
Optimizer::PassToken CreateSpreadVolatileSemanticsPass();
// Create a pass to replace a descriptor access using variable index.
// This pass replaces every access using a variable index to array variable
// |desc| that has a DescriptorSet and Binding decorations with a constant

Просмотреть файл

@ -108,6 +108,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
scalar_replacement_pass.h
set_spec_constant_default_value_pass.h
simplification_pass.h
spread_volatile_semantics.h
ssa_rewrite_pass.h
strength_reduction_pass.h
strip_debug_info_pass.h
@ -215,6 +216,7 @@ set(SPIRV_TOOLS_OPT_SOURCES
scalar_replacement_pass.cpp
set_spec_constant_default_value_pass.cpp
simplification_pass.cpp
spread_volatile_semantics.cpp
ssa_rewrite_pass.cpp
strength_reduction_pass.cpp
strip_debug_info_pass.cpp

Просмотреть файл

@ -324,6 +324,8 @@ bool Optimizer::RegisterPassFromFlag(const std::string& flag) {
RegisterPass(CreateLocalAccessChainConvertPass());
} else if (pass_name == "replace-desc-array-access-using-var-index") {
RegisterPass(CreateReplaceDescArrayAccessUsingVarIndexPass());
} else if (pass_name == "spread-volatile-semantics") {
RegisterPass(CreateSpreadVolatileSemanticsPass());
} else if (pass_name == "descriptor-scalar-replacement") {
RegisterPass(CreateDescriptorScalarReplacementPass());
} else if (pass_name == "eliminate-dead-code-aggressive") {
@ -976,6 +978,11 @@ Optimizer::PassToken CreateReplaceDescArrayAccessUsingVarIndexPass() {
MakeUnique<opt::ReplaceDescArrayAccessUsingVarIndex>());
}
Optimizer::PassToken CreateSpreadVolatileSemanticsPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::SpreadVolatileSemantics>());
}
Optimizer::PassToken CreateDescriptorScalarReplacementPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::DescriptorScalarReplacement>());

Просмотреть файл

@ -71,6 +71,7 @@
#include "source/opt/scalar_replacement_pass.h"
#include "source/opt/set_spec_constant_default_value_pass.h"
#include "source/opt/simplification_pass.h"
#include "source/opt/spread_volatile_semantics.h"
#include "source/opt/ssa_rewrite_pass.h"
#include "source/opt/strength_reduction_pass.h"
#include "source/opt/strip_debug_info_pass.h"

Просмотреть файл

@ -0,0 +1,314 @@
// Copyright (c) 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "source/opt/spread_volatile_semantics.h"
#include "source/opt/decoration_manager.h"
#include "source/opt/ir_builder.h"
#include "source/spirv_constant.h"
namespace spvtools {
namespace opt {
namespace {
const uint32_t kOpDecorateInOperandBuiltinDecoration = 2u;
const uint32_t kOpLoadInOperandMemoryOperands = 1u;
const uint32_t kOpEntryPointInOperandEntryPoint = 1u;
const uint32_t kOpEntryPointInOperandInterface = 3u;
bool HasBuiltinDecoration(analysis::DecorationManager* decoration_manager,
uint32_t var_id, uint32_t built_in) {
return decoration_manager->FindDecoration(
var_id, SpvDecorationBuiltIn, [built_in](const Instruction& inst) {
return built_in == inst.GetSingleWordInOperand(
kOpDecorateInOperandBuiltinDecoration);
});
}
bool IsBuiltInForRayTracingVolatileSemantics(uint32_t built_in) {
switch (built_in) {
case SpvBuiltInSMIDNV:
case SpvBuiltInWarpIDNV:
case SpvBuiltInSubgroupSize:
case SpvBuiltInSubgroupLocalInvocationId:
case SpvBuiltInSubgroupEqMask:
case SpvBuiltInSubgroupGeMask:
case SpvBuiltInSubgroupGtMask:
case SpvBuiltInSubgroupLeMask:
case SpvBuiltInSubgroupLtMask:
return true;
default:
return false;
}
}
bool HasBuiltinForRayTracingVolatileSemantics(
analysis::DecorationManager* decoration_manager, uint32_t var_id) {
return decoration_manager->FindDecoration(
var_id, SpvDecorationBuiltIn, [](const Instruction& inst) {
uint32_t built_in =
inst.GetSingleWordInOperand(kOpDecorateInOperandBuiltinDecoration);
return IsBuiltInForRayTracingVolatileSemantics(built_in);
});
}
bool HasVolatileDecoration(analysis::DecorationManager* decoration_manager,
uint32_t var_id) {
return decoration_manager->HasDecoration(var_id, SpvDecorationVolatile);
}
bool HasOnlyEntryPointsAsFunctions(IRContext* context, Module* module) {
std::unordered_set<uint32_t> entry_function_ids;
for (Instruction& entry_point : module->entry_points()) {
entry_function_ids.insert(
entry_point.GetSingleWordInOperand(kOpEntryPointInOperandEntryPoint));
}
for (auto& function : *module) {
if (entry_function_ids.find(function.result_id()) ==
entry_function_ids.end()) {
std::string message(
"Functions of SPIR-V for spread-volatile-semantics pass input must "
"be inlined except entry points");
message += "\n " + function.DefInst().PrettyPrint(
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
context->consumer()(SPV_MSG_ERROR, "", {0, 0, 0}, message.c_str());
return false;
}
}
return true;
}
} // namespace
Pass::Status SpreadVolatileSemantics::Process() {
if (!HasOnlyEntryPointsAsFunctions(context(), get_module())) {
return Status::Failure;
}
const bool is_vk_memory_model_enabled =
context()->get_feature_mgr()->HasCapability(
SpvCapabilityVulkanMemoryModel);
CollectTargetsForVolatileSemantics(is_vk_memory_model_enabled);
// If VulkanMemoryModel capability is not enabled, we have to set Volatile
// decoration for interface variables instead of setting Volatile for load
// instructions. If an interface (or pointers to it) is used by two load
// instructions in two entry points and one must be volatile while another
// is not, we have to report an error for the conflict.
if (!is_vk_memory_model_enabled &&
HasInterfaceInConflictOfVolatileSemantics()) {
return Status::Failure;
}
return SpreadVolatileSemanticsToVariables(is_vk_memory_model_enabled);
}
Pass::Status SpreadVolatileSemantics::SpreadVolatileSemanticsToVariables(
const bool is_vk_memory_model_enabled) {
Status status = Status::SuccessWithoutChange;
for (Instruction& var : context()->types_values()) {
auto entry_function_ids =
EntryFunctionsToSpreadVolatileSemanticsForVar(var.result_id());
if (entry_function_ids.empty()) {
continue;
}
if (is_vk_memory_model_enabled) {
SetVolatileForLoadsInEntries(&var, entry_function_ids);
} else {
DecorateVarWithVolatile(&var);
}
status = Status::SuccessWithChange;
}
return status;
}
bool SpreadVolatileSemantics::IsTargetUsedByNonVolatileLoadInEntryPoint(
uint32_t var_id, Instruction* entry_point) {
uint32_t entry_function_id =
entry_point->GetSingleWordInOperand(kOpEntryPointInOperandEntryPoint);
return !VisitLoadsOfPointersToVariableInEntries(
var_id,
[](Instruction* load) {
// If it has a load without volatile memory operand, finish traversal
// and return false.
if (load->NumInOperands() <= kOpLoadInOperandMemoryOperands) {
return false;
}
uint32_t memory_operands =
load->GetSingleWordInOperand(kOpLoadInOperandMemoryOperands);
return (memory_operands & SpvMemoryAccessVolatileMask) != 0;
},
{entry_function_id});
}
bool SpreadVolatileSemantics::HasInterfaceInConflictOfVolatileSemantics() {
for (Instruction& entry_point : get_module()->entry_points()) {
SpvExecutionModel execution_model =
static_cast<SpvExecutionModel>(entry_point.GetSingleWordInOperand(0));
for (uint32_t operand_index = kOpEntryPointInOperandInterface;
operand_index < entry_point.NumInOperands(); ++operand_index) {
uint32_t var_id = entry_point.GetSingleWordInOperand(operand_index);
if (!EntryFunctionsToSpreadVolatileSemanticsForVar(var_id).empty() &&
!IsTargetForVolatileSemantics(var_id, execution_model) &&
IsTargetUsedByNonVolatileLoadInEntryPoint(var_id, &entry_point)) {
Instruction* inst = context()->get_def_use_mgr()->GetDef(var_id);
context()->EmitErrorMessage(
"Variable is a target for Volatile semantics for an entry point, "
"but it is not for another entry point",
inst);
return true;
}
}
}
return false;
}
void SpreadVolatileSemantics::MarkVolatileSemanticsForVariable(
uint32_t var_id, Instruction* entry_point) {
uint32_t entry_function_id =
entry_point->GetSingleWordInOperand(kOpEntryPointInOperandEntryPoint);
auto itr = var_ids_to_entry_fn_for_volatile_semantics_.find(var_id);
if (itr == var_ids_to_entry_fn_for_volatile_semantics_.end()) {
var_ids_to_entry_fn_for_volatile_semantics_[var_id] = {entry_function_id};
return;
}
itr->second.insert(entry_function_id);
}
void SpreadVolatileSemantics::CollectTargetsForVolatileSemantics(
const bool is_vk_memory_model_enabled) {
for (Instruction& entry_point : get_module()->entry_points()) {
SpvExecutionModel execution_model =
static_cast<SpvExecutionModel>(entry_point.GetSingleWordInOperand(0));
for (uint32_t operand_index = kOpEntryPointInOperandInterface;
operand_index < entry_point.NumInOperands(); ++operand_index) {
uint32_t var_id = entry_point.GetSingleWordInOperand(operand_index);
if (!IsTargetForVolatileSemantics(var_id, execution_model)) {
continue;
}
if (is_vk_memory_model_enabled ||
IsTargetUsedByNonVolatileLoadInEntryPoint(var_id, &entry_point)) {
MarkVolatileSemanticsForVariable(var_id, &entry_point);
}
}
}
}
void SpreadVolatileSemantics::DecorateVarWithVolatile(Instruction* var) {
analysis::DecorationManager* decoration_manager =
context()->get_decoration_mgr();
uint32_t var_id = var->result_id();
if (HasVolatileDecoration(decoration_manager, var_id)) {
return;
}
get_decoration_mgr()->AddDecoration(
SpvOpDecorate, {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {var_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_LITERAL_INTEGER,
{SpvDecorationVolatile}}});
}
bool SpreadVolatileSemantics::VisitLoadsOfPointersToVariableInEntries(
uint32_t var_id, const std::function<bool(Instruction*)>& handle_load,
const std::unordered_set<uint32_t>& entry_function_ids) {
std::vector<uint32_t> worklist({var_id});
auto* def_use_mgr = context()->get_def_use_mgr();
while (!worklist.empty()) {
uint32_t ptr_id = worklist.back();
worklist.pop_back();
bool finish_traversal = !def_use_mgr->WhileEachUser(
ptr_id, [this, &worklist, &ptr_id, handle_load,
&entry_function_ids](Instruction* user) {
BasicBlock* block = context()->get_instr_block(user);
if (block == nullptr ||
entry_function_ids.find(block->GetParent()->result_id()) ==
entry_function_ids.end()) {
return true;
}
if (user->opcode() == SpvOpAccessChain ||
user->opcode() == SpvOpInBoundsAccessChain ||
user->opcode() == SpvOpPtrAccessChain ||
user->opcode() == SpvOpInBoundsPtrAccessChain ||
user->opcode() == SpvOpCopyObject) {
if (ptr_id == user->GetSingleWordInOperand(0))
worklist.push_back(user->result_id());
return true;
}
if (user->opcode() != SpvOpLoad) {
return true;
}
return handle_load(user);
});
if (finish_traversal) return false;
}
return true;
}
void SpreadVolatileSemantics::SetVolatileForLoadsInEntries(
Instruction* var, const std::unordered_set<uint32_t>& entry_function_ids) {
// Set Volatile memory operand for all load instructions if they do not have
// it.
VisitLoadsOfPointersToVariableInEntries(
var->result_id(),
[](Instruction* load) {
if (load->NumInOperands() <= kOpLoadInOperandMemoryOperands) {
load->AddOperand(
{SPV_OPERAND_TYPE_MEMORY_ACCESS, {SpvMemoryAccessVolatileMask}});
return true;
}
uint32_t memory_operands =
load->GetSingleWordInOperand(kOpLoadInOperandMemoryOperands);
memory_operands |= SpvMemoryAccessVolatileMask;
load->SetInOperand(kOpLoadInOperandMemoryOperands, {memory_operands});
return true;
},
entry_function_ids);
}
bool SpreadVolatileSemantics::IsTargetForVolatileSemantics(
uint32_t var_id, SpvExecutionModel execution_model) {
analysis::DecorationManager* decoration_manager =
context()->get_decoration_mgr();
if (execution_model == SpvExecutionModelFragment) {
return get_module()->version() >= SPV_SPIRV_VERSION_WORD(1, 6) &&
HasBuiltinDecoration(decoration_manager, var_id,
SpvBuiltInHelperInvocation);
}
if (execution_model == SpvExecutionModelIntersectionKHR ||
execution_model == SpvExecutionModelIntersectionNV) {
if (HasBuiltinDecoration(decoration_manager, var_id,
SpvBuiltInRayTmaxKHR)) {
return true;
}
}
switch (execution_model) {
case SpvExecutionModelRayGenerationKHR:
case SpvExecutionModelClosestHitKHR:
case SpvExecutionModelMissKHR:
case SpvExecutionModelCallableKHR:
case SpvExecutionModelIntersectionKHR:
return HasBuiltinForRayTracingVolatileSemantics(decoration_manager,
var_id);
default:
return false;
}
}
} // namespace opt
} // namespace spvtools

Просмотреть файл

@ -0,0 +1,110 @@
// Copyright (c) 2022 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_SPREAD_VOLATILE_SEMANTICS_H_
#define SOURCE_OPT_SPREAD_VOLATILE_SEMANTICS_H_
#include "source/opt/pass.h"
namespace spvtools {
namespace opt {
// See optimizer.hpp for documentation.
class SpreadVolatileSemantics : public Pass {
public:
SpreadVolatileSemantics() {}
const char* name() const override { return "spread-volatile-semantics"; }
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
return IRContext::kAnalysisDefUse | IRContext::kAnalysisDecorations |
IRContext::kAnalysisInstrToBlockMapping;
}
private:
// Iterates interface variables and spreads the Volatile semantics if it has
// load instructions for the Volatile semantics.
Pass::Status SpreadVolatileSemanticsToVariables(
const bool is_vk_memory_model_enabled);
// Returns whether |var_id| is the result id of a target builtin variable for
// the volatile semantics for |execution_model| based on the Vulkan spec
// VUID-StandaloneSpirv-VulkanMemoryModel-04678 or
// VUID-StandaloneSpirv-VulkanMemoryModel-04679.
bool IsTargetForVolatileSemantics(uint32_t var_id,
SpvExecutionModel execution_model);
// Collects interface variables that need the volatile semantics.
// |is_vk_memory_model_enabled| is true if VulkanMemoryModel capability is
// enabled.
void CollectTargetsForVolatileSemantics(
const bool is_vk_memory_model_enabled);
// Reports an error if an interface variable is used by two entry points and
// it needs the Volatile decoration for one but not for another. Returns true
// if the error must be reported.
bool HasInterfaceInConflictOfVolatileSemantics();
// Returns whether the variable whose result is |var_id| is used by a
// non-volatile load or a pointer to it is used by a non-volatile load in
// |entry_point| or not.
bool IsTargetUsedByNonVolatileLoadInEntryPoint(uint32_t var_id,
Instruction* entry_point);
// Visits load instructions of pointers to variable whose result id is
// |var_id| if the load instructions are in entry points whose
// function id is one of |entry_function_ids|. |handle_load| is a function to
// do some actions for the load instructions. Finishes the traversal and
// returns false if |handle_load| returns false for a load instruction.
// Otherwise, returns true after running |handle_load| for all the load
// instructions.
bool VisitLoadsOfPointersToVariableInEntries(
uint32_t var_id, const std::function<bool(Instruction*)>& handle_load,
const std::unordered_set<uint32_t>& entry_function_ids);
// Sets Memory Operands of OpLoad instructions that load |var| or pointers
// of |var| as Volatile if the function id of the OpLoad instruction is
// included in |entry_function_ids|.
void SetVolatileForLoadsInEntries(
Instruction* var, const std::unordered_set<uint32_t>& entry_function_ids);
// Adds OpDecorate Volatile for |var| if it does not exist.
void DecorateVarWithVolatile(Instruction* var);
// Returns a set of entry function ids to spread the volatile semantics for
// the variable with the result id |var_id|.
std::unordered_set<uint32_t> EntryFunctionsToSpreadVolatileSemanticsForVar(
uint32_t var_id) {
auto itr = var_ids_to_entry_fn_for_volatile_semantics_.find(var_id);
if (itr == var_ids_to_entry_fn_for_volatile_semantics_.end()) return {};
return itr->second;
}
// Specifies that we have to spread the volatile semantics for the
// variable with the result id |var_id| for the entry point |entry_point|.
void MarkVolatileSemanticsForVariable(uint32_t var_id,
Instruction* entry_point);
// Result ids of variables to entry function ids for the volatile semantics
// spread.
std::unordered_map<uint32_t, std::unordered_set<uint32_t>>
var_ids_to_entry_fn_for_volatile_semantics_;
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_SPREAD_VOLATILE_SEMANTICS_H_

Просмотреть файл

@ -91,6 +91,7 @@ add_spvtools_unittest(TARGET opt
scalar_replacement_test.cpp
set_spec_const_default_value_test.cpp
simplification_test.cpp
spread_volatile_semantics_test.cpp
strength_reduction_test.cpp
strip_debug_info_test.cpp
strip_nonsemantic_info_test.cpp

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -168,6 +168,16 @@ Options (in lexicographical order):)",
with a switch that has a case for every possible value of the
index.)");
printf(R"(
--spread-volatile-semantics
Spread Volatile semantics to variables with SMIDNV, WarpIDNV,
SubgroupSize, SubgroupLocalInvocationId, SubgroupEqMask,
SubgroupGeMask, SubgroupGtMask, SubgroupLeMask, or SubgroupLtMask
BuiltIn decorations or OpLoad for them when the shader model is
ray generation, closest hit, miss, intersection, or callable.
For the SPIR-V version is 1.6 or above, it also spreads Volatile
semantics to a variable with HelperInvocation BuiltIn decoration
in the fragement shader.)");
printf(R"(
--descriptor-scalar-replacement
Replaces every array variable |desc| that has a DescriptorSet
and Binding decorations with a new variable for each element of

Просмотреть файл

@ -43,7 +43,7 @@ AUTHORS = ['The Khronos Group Inc.',
'ZHOU He']
CURRENT_YEAR='2021'
YEARS = '(2014-2016|2015-2016|2015-2020|2016|2016-2017|2017|2017-2019|2018|2019|2020|2021)'
YEARS = '(2014-2016|2015-2016|2015-2020|2016|2016-2017|2017|2017-2019|2018|2019|2020|2021|2022)'
COPYRIGHT_RE = re.compile(
'Copyright \(c\) {} ({})'.format(YEARS, '|'.join(AUTHORS)))