mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-29 06:21:06 +00:00
Copy propagate arrays
The sprir-v generated from HLSL code contain many copyies of very large arrays. Not only are these time consumming, but they also cause problems for drivers because they require too much space. To work around this, we will implement an array copy propagation. Note that we will not implement a complete array data flow analysis in order to implement this. We will be looking for very simple cases: 1) The source must never be stored to. 2) The target must be stored to exactly once. 3) The store to the target must be a store to the entire array, and be a copy of the entire source. 4) All loads of the target must be dominated by the store. The hard part is keeping all of the types correct. We do not want to have to do too large a search to update everything, which may not be possible, do we give up if we see any instruction that might be hard to update. Also in types.h, the element decorations are not stored in an std::map. This change was done so the hashing algorithm for a Struct is consistent. With the std::unordered_map, the traversal order was non-deterministic leading to the same type getting hashed to different values. See |Struct::GetExtraHashWords|. Contributes to #1416.
This commit is contained in:
parent
0a8b6a96e1
commit
c4dc046399
@ -70,6 +70,7 @@ SPVTOOLS_OPT_SRC_FILES := \
|
||||
source/opt/composite.cpp \
|
||||
source/opt/const_folding_rules.cpp \
|
||||
source/opt/constants.cpp \
|
||||
source/opt/copy_prop_arrays.cpp \
|
||||
source/opt/dead_branch_elim_pass.cpp \
|
||||
source/opt/dead_insert_elim_pass.cpp \
|
||||
source/opt/dead_variable_elimination.cpp \
|
||||
|
@ -548,6 +548,10 @@ Optimizer::PassToken CreateLoopUnrollPass(bool fully_unroll, int factor = 0);
|
||||
// processed (see IsSSATargetVar for details).
|
||||
Optimizer::PassToken CreateSSARewritePass();
|
||||
|
||||
// Create copy propagate arrays pass.
|
||||
// This pass looks to copy propagate memory references for arrays. It looks
|
||||
// for specific code patterns to recognize array copies.
|
||||
Optimizer::PassToken CreateCopyPropagateArraysPass();
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SPIRV_TOOLS_OPTIMIZER_HPP_
|
||||
|
@ -24,6 +24,7 @@ add_library(SPIRV-Tools-opt
|
||||
composite.h
|
||||
const_folding_rules.h
|
||||
constants.h
|
||||
copy_prop_arrays.h
|
||||
dead_branch_elim_pass.h
|
||||
dead_insert_elim_pass.h
|
||||
dead_variable_elimination.h
|
||||
@ -102,6 +103,7 @@ add_library(SPIRV-Tools-opt
|
||||
composite.cpp
|
||||
const_folding_rules.cpp
|
||||
constants.cpp
|
||||
copy_prop_arrays.cpp
|
||||
dead_branch_elim_pass.cpp
|
||||
dead_insert_elim_pass.cpp
|
||||
dead_variable_elimination.cpp
|
||||
|
565
source/opt/copy_prop_arrays.cpp
Normal file
565
source/opt/copy_prop_arrays.cpp
Normal file
@ -0,0 +1,565 @@
|
||||
// Copyright (c) 2018 Google LLC.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "copy_prop_arrays.h"
|
||||
#include "ir_builder.h"
|
||||
|
||||
namespace {
|
||||
const uint32_t kLoadPointerInOperand = 0;
|
||||
const uint32_t kStorePointerInOperand = 0;
|
||||
const uint32_t kStoreObjectInOperand = 1;
|
||||
const uint32_t kCompositeExtractObjectInOperand = 0;
|
||||
} // namespace
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
Pass::Status CopyPropagateArrays::Process(ir::IRContext* ctx) {
|
||||
InitializeProcessing(ctx);
|
||||
|
||||
bool modified = false;
|
||||
for (ir::Function& function : *get_module()) {
|
||||
ir::BasicBlock* entry_bb = &*function.begin();
|
||||
|
||||
for (auto var_inst = entry_bb->begin(); var_inst->opcode() == SpvOpVariable;
|
||||
++var_inst) {
|
||||
if (!IsPointerToArrayType(var_inst->type_id())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::unique_ptr<MemoryObject> source_object =
|
||||
FindSourceObjectIfPossible(&*var_inst);
|
||||
|
||||
if (source_object != nullptr) {
|
||||
if (CanUpdateUses(&*var_inst, source_object->GetPointerTypeId())) {
|
||||
modified = true;
|
||||
PropagateObject(&*var_inst, source_object.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (modified ? Status::SuccessWithChange : Status::SuccessWithoutChange);
|
||||
}
|
||||
|
||||
std::unique_ptr<CopyPropagateArrays::MemoryObject>
|
||||
CopyPropagateArrays::FindSourceObjectIfPossible(ir::Instruction* var_inst) {
|
||||
assert(var_inst->opcode() == SpvOpVariable && "Expecting a variable.");
|
||||
|
||||
// Check that the variable is a composite object with single store that
|
||||
// dominates all of its loads.
|
||||
|
||||
// Find the only store to the entire memory location, if it exists.
|
||||
ir::Instruction* store_inst = nullptr;
|
||||
get_def_use_mgr()->WhileEachUser(
|
||||
var_inst, [&store_inst, var_inst](ir::Instruction* use) {
|
||||
if (use->opcode() == SpvOpStore &&
|
||||
use->GetSingleWordInOperand(kStorePointerInOperand) ==
|
||||
var_inst->result_id()) {
|
||||
if (store_inst == nullptr) {
|
||||
store_inst = use;
|
||||
} else {
|
||||
store_inst = nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!store_inst) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Look at the loads to ensure they are dominated by the store.
|
||||
if (!HasValidReferencesOnly(var_inst, store_inst)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If so, look at the store to see if it is the copy of an object.
|
||||
std::unique_ptr<MemoryObject> source = GetSourceObjectIfAny(
|
||||
store_inst->GetSingleWordInOperand(kStoreObjectInOperand));
|
||||
|
||||
if (!source) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Ensure that |source| does not change between the point at which it is
|
||||
// loaded, and the position in which |var_inst| is loaded.
|
||||
//
|
||||
// For now we will go with the easy to implement approach, and check that the
|
||||
// entire variable (not just the specific component) is never written to.
|
||||
|
||||
if (!HasNoStores(source->GetVariable())) {
|
||||
return nullptr;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
void CopyPropagateArrays::PropagateObject(ir::Instruction* var_inst,
|
||||
MemoryObject* source) {
|
||||
assert(var_inst->opcode() == SpvOpVariable &&
|
||||
"This function propagates variables.");
|
||||
|
||||
ir::Instruction* insertion_point = var_inst->NextNode();
|
||||
while (insertion_point->opcode() == SpvOpVariable) {
|
||||
insertion_point = insertion_point->NextNode();
|
||||
}
|
||||
ir::Instruction* new_access_chain =
|
||||
BuildNewAccessChain(insertion_point, source);
|
||||
context()->KillNamesAndDecorates(var_inst);
|
||||
UpdateUses(var_inst, new_access_chain);
|
||||
}
|
||||
|
||||
ir::Instruction* CopyPropagateArrays::BuildNewAccessChain(
|
||||
ir::Instruction* insertion_point,
|
||||
CopyPropagateArrays::MemoryObject* source) const {
|
||||
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
|
||||
|
||||
InstructionBuilder builder(context(), insertion_point,
|
||||
ir::IRContext::kAnalysisDefUse |
|
||||
ir::IRContext::kAnalysisInstrToBlockMapping);
|
||||
|
||||
analysis::Integer int_type(32, false);
|
||||
const analysis::Type* uint32_type =
|
||||
context()->get_type_mgr()->GetRegisteredType(&int_type);
|
||||
|
||||
// Convert the access chain in the source to a series of ids that can be used
|
||||
// by the |OpAccessChain| instruction.
|
||||
std::vector<uint32_t> index_ids;
|
||||
for (uint32_t index : source->AccessChain()) {
|
||||
const analysis::Constant* index_const =
|
||||
const_mgr->GetConstant(uint32_type, {index});
|
||||
index_ids.push_back(
|
||||
const_mgr->GetDefiningInstruction(index_const)->result_id());
|
||||
}
|
||||
|
||||
// Get the type for the result of the OpAccessChain
|
||||
uint32_t pointer_type_id = source->GetPointerTypeId();
|
||||
|
||||
// Build the access chain instruction.
|
||||
return builder.AddAccessChain(pointer_type_id,
|
||||
source->GetVariable()->result_id(), index_ids);
|
||||
}
|
||||
|
||||
bool CopyPropagateArrays::HasNoStores(ir::Instruction* ptr_inst) {
|
||||
return get_def_use_mgr()->WhileEachUser(
|
||||
ptr_inst, [this](ir::Instruction* use) {
|
||||
if (use->opcode() == SpvOpLoad) {
|
||||
return true;
|
||||
} else if (use->opcode() == SpvOpAccessChain) {
|
||||
return HasNoStores(use);
|
||||
} else if (use->IsDecoration() || use->opcode() == SpvOpName) {
|
||||
return true;
|
||||
} else if (use->opcode() == SpvOpStore) {
|
||||
return false;
|
||||
}
|
||||
// Some other instruction. Be conservative.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
bool CopyPropagateArrays::HasValidReferencesOnly(ir::Instruction* ptr_inst,
|
||||
ir::Instruction* store_inst) {
|
||||
ir::BasicBlock* store_block = context()->get_instr_block(store_inst);
|
||||
opt::DominatorAnalysis* dominator_analysis =
|
||||
context()->GetDominatorAnalysis(store_block->GetParent(), *cfg());
|
||||
|
||||
return get_def_use_mgr()->WhileEachUser(
|
||||
ptr_inst,
|
||||
[this, store_inst, dominator_analysis, ptr_inst](ir::Instruction* use) {
|
||||
if (use->opcode() == SpvOpLoad) {
|
||||
// TODO: If there are many load in the same BB as |store_inst| the
|
||||
// time to do the multiple traverses can add up. Consider collecting
|
||||
// those loads and doing a single traversal.
|
||||
return dominator_analysis->Dominates(store_inst, use);
|
||||
} else if (use->opcode() == SpvOpAccessChain) {
|
||||
return HasValidReferencesOnly(use, store_inst);
|
||||
} else if (use->IsDecoration() || use->opcode() == SpvOpName) {
|
||||
return true;
|
||||
} else if (use->opcode() == SpvOpStore) {
|
||||
// If we are storing to part of the object it is not an candidate.
|
||||
return ptr_inst->opcode() == SpvOpVariable &&
|
||||
store_inst->GetSingleWordInOperand(kStorePointerInOperand) ==
|
||||
ptr_inst->result_id();
|
||||
}
|
||||
// Some other instruction. Be conservative.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<CopyPropagateArrays::MemoryObject>
|
||||
CopyPropagateArrays::GetSourceObjectIfAny(uint32_t result) {
|
||||
ir::Instruction* result_inst = context()->get_def_use_mgr()->GetDef(result);
|
||||
|
||||
switch (result_inst->opcode()) {
|
||||
case SpvOpLoad:
|
||||
return BuildMemoryObjectFromLoad(result_inst);
|
||||
case SpvOpCompositeExtract:
|
||||
return BuildMemoryObjectFromExtract(result_inst);
|
||||
case SpvOpCompositeConstruct:
|
||||
return BuildMemoryObjectFromCompositeConstruct(result_inst);
|
||||
case SpvOpCopyObject:
|
||||
return GetSourceObjectIfAny(result_inst->GetSingleWordInOperand(0));
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<CopyPropagateArrays::MemoryObject>
|
||||
CopyPropagateArrays::BuildMemoryObjectFromLoad(ir::Instruction* load_inst) {
|
||||
std::vector<uint32_t> components_in_reverse;
|
||||
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
|
||||
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
|
||||
|
||||
ir::Instruction* current_inst = def_use_mgr->GetDef(
|
||||
load_inst->GetSingleWordInOperand(kLoadPointerInOperand));
|
||||
|
||||
// Build the access chain for the memory object by collecting the indices used
|
||||
// in the OpAccessChain instructions. If we find a variable index, then
|
||||
// return |nullptr| because we cannot know for sure which memory location is
|
||||
// used.
|
||||
//
|
||||
// It is built in reverse order because the different |OpAccessChain|
|
||||
// instructions are visited in reverse order from which they are applied.
|
||||
while (current_inst->opcode() == SpvOpAccessChain) {
|
||||
for (uint32_t i = current_inst->NumInOperands() - 1; i >= 1; --i) {
|
||||
uint32_t element_index_id = current_inst->GetSingleWordInOperand(i);
|
||||
const analysis::Constant* element_index_const =
|
||||
const_mgr->FindDeclaredConstant(element_index_id);
|
||||
if (!element_index_const) {
|
||||
return nullptr;
|
||||
}
|
||||
assert(element_index_const->AsIntConstant());
|
||||
components_in_reverse.push_back(
|
||||
element_index_const->AsIntConstant()->GetU32());
|
||||
}
|
||||
current_inst = def_use_mgr->GetDef(current_inst->GetSingleWordInOperand(0));
|
||||
}
|
||||
|
||||
// If the address in the load is not constructed from an |OpVariable|
|
||||
// instruction followed by a series of |OpAccessChain| instructions, then
|
||||
// return |nullptr| because we cannot identify the owner or access chain
|
||||
// exactly.
|
||||
if (current_inst->opcode() != SpvOpVariable) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Build the memory object. Use |rbegin| and |rend| to put the access chain
|
||||
// back in the correct order.
|
||||
return std::unique_ptr<CopyPropagateArrays::MemoryObject>(
|
||||
new MemoryObject(current_inst, components_in_reverse.rbegin(),
|
||||
components_in_reverse.rend()));
|
||||
}
|
||||
|
||||
std::unique_ptr<CopyPropagateArrays::MemoryObject>
|
||||
CopyPropagateArrays::BuildMemoryObjectFromExtract(
|
||||
ir::Instruction* extract_inst) {
|
||||
assert(extract_inst->opcode() == SpvOpCompositeExtract &&
|
||||
"Expecting an OpCompositeExtract instruction.");
|
||||
|
||||
std::unique_ptr<MemoryObject> result = GetSourceObjectIfAny(
|
||||
extract_inst->GetSingleWordInOperand(kCompositeExtractObjectInOperand));
|
||||
|
||||
if (result) {
|
||||
std::vector<uint32_t> components;
|
||||
for (uint32_t i = 1; i < extract_inst->NumInOperands(); ++i) {
|
||||
components.emplace_back(extract_inst->GetSingleWordInOperand(i));
|
||||
}
|
||||
result->GetMember(components);
|
||||
return result;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<CopyPropagateArrays::MemoryObject>
|
||||
CopyPropagateArrays::BuildMemoryObjectFromCompositeConstruct(
|
||||
ir::Instruction* conststruct_inst) {
|
||||
assert(conststruct_inst->opcode() == SpvOpCompositeConstruct &&
|
||||
"Expecting an OpCompositeConstruct instruction.");
|
||||
|
||||
// If every operand in the instruction are part of the same memory object, and
|
||||
// are being combined in the same order, then the result is the same as the
|
||||
// parent.
|
||||
|
||||
std::unique_ptr<MemoryObject> memory_object =
|
||||
GetSourceObjectIfAny(conststruct_inst->GetSingleWordInOperand(0));
|
||||
|
||||
if (!memory_object) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!memory_object->IsMember()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (memory_object->AccessChain().back() != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
memory_object->GetParent();
|
||||
|
||||
if (memory_object->GetNumberOfMembers() !=
|
||||
conststruct_inst->NumInOperands()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (uint32_t i = 1; i < conststruct_inst->NumInOperands(); ++i) {
|
||||
std::unique_ptr<MemoryObject> member_object =
|
||||
GetSourceObjectIfAny(conststruct_inst->GetSingleWordInOperand(i));
|
||||
|
||||
if (member_object->GetVariable() != memory_object->GetVariable()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (member_object->AccessChain().back() != i) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return memory_object;
|
||||
}
|
||||
|
||||
bool CopyPropagateArrays::IsPointerToArrayType(uint32_t type_id) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::Pointer* pointer_type = type_mgr->GetType(type_id)->AsPointer();
|
||||
if (pointer_type) {
|
||||
return pointer_type->pointee_type()->AsArray() != nullptr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CopyPropagateArrays::CanUpdateUses(ir::Instruction* original_ptr_inst,
|
||||
uint32_t type_id) {
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
|
||||
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
|
||||
|
||||
return def_use_mgr->WhileEachUse(original_ptr_inst, [this, type_id, type_mgr,
|
||||
const_mgr](
|
||||
ir::Instruction* use,
|
||||
uint32_t index) {
|
||||
analysis::Pointer* pointer_type = nullptr;
|
||||
switch (use->opcode()) {
|
||||
case SpvOpLoad: {
|
||||
pointer_type = type_mgr->GetType(type_id)->AsPointer();
|
||||
uint32_t new_type_id = type_mgr->GetId(pointer_type->pointee_type());
|
||||
|
||||
if (new_type_id != use->type_id()) {
|
||||
return CanUpdateUses(use, new_type_id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SpvOpAccessChain: {
|
||||
pointer_type = type_mgr->GetType(type_id)->AsPointer();
|
||||
const analysis::Type* pointee_type = pointer_type->pointee_type();
|
||||
|
||||
std::vector<uint32_t> access_chain;
|
||||
for (uint32_t i = 1; i < use->NumInOperands(); ++i) {
|
||||
const analysis::Constant* index_const =
|
||||
const_mgr->FindDeclaredConstant(use->GetSingleWordInOperand(i));
|
||||
if (index_const) {
|
||||
access_chain.push_back(index_const->AsIntConstant()->GetU32());
|
||||
} else {
|
||||
// Variable index means the type is a type where every element
|
||||
// is the same type. Use element 0 to get the type.
|
||||
access_chain.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
const analysis::Type* new_pointee_type =
|
||||
type_mgr->GetMemberType(pointee_type, access_chain);
|
||||
opt::analysis::Pointer pointerTy(new_pointee_type,
|
||||
pointer_type->storage_class());
|
||||
uint32_t new_pointer_type_id =
|
||||
context()->get_type_mgr()->GetTypeInstruction(&pointerTy);
|
||||
|
||||
if (new_pointer_type_id != use->type_id()) {
|
||||
return CanUpdateUses(use, new_pointer_type_id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SpvOpCompositeExtract: {
|
||||
std::vector<uint32_t> access_chain;
|
||||
for (uint32_t i = 1; i < use->NumInOperands(); ++i) {
|
||||
const analysis::Constant* index_const =
|
||||
const_mgr->FindDeclaredConstant(use->GetSingleWordInOperand(i));
|
||||
if (index_const) {
|
||||
access_chain.push_back(index_const->AsIntConstant()->GetU32());
|
||||
} else {
|
||||
// Variable index means the type is an type where every element
|
||||
// is the same type. Use element 0 to get the type.
|
||||
access_chain.push_back(0);
|
||||
}
|
||||
}
|
||||
|
||||
const analysis::Type* type = type_mgr->GetType(type_id);
|
||||
const analysis::Type* new_type =
|
||||
type_mgr->GetMemberType(type, access_chain);
|
||||
uint32_t new_type_id = type_mgr->GetTypeInstruction(new_type);
|
||||
|
||||
if (new_type_id != use->type_id()) {
|
||||
return CanUpdateUses(use, new_type_id);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case SpvOpStore:
|
||||
// Can't handle changing the type of a store. There are too many other
|
||||
// things that might need to change as well. Not worth the effort.
|
||||
// Punting for now.
|
||||
|
||||
// TODO (s-perron): This can be handled by expanding the store into a
|
||||
// series of extracts, composite constructs, and a store.
|
||||
return index != 1;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
void CopyPropagateArrays::UpdateUses(ir::Instruction* original_ptr_inst,
|
||||
ir::Instruction* new_ptr_inst) {
|
||||
// TODO (s-perron): Keep the def-use manager up to date. Not done now because
|
||||
// it can cause problems for the |ForEachUse| traversals. Can be use by
|
||||
// keeping a list of instructions that need updating, and then updating them
|
||||
// in |PropagateObject|.
|
||||
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
|
||||
analysis::DefUseManager* def_use_mgr = context()->get_def_use_mgr();
|
||||
|
||||
def_use_mgr->ForEachUse(original_ptr_inst, [this, new_ptr_inst, type_mgr,
|
||||
const_mgr](ir::Instruction* use,
|
||||
uint32_t index) {
|
||||
analysis::Pointer* pointer_type = nullptr;
|
||||
switch (use->opcode()) {
|
||||
case SpvOpLoad: {
|
||||
// Replace the actual use.
|
||||
use->SetOperand(index, {new_ptr_inst->result_id()});
|
||||
|
||||
// Update the type.
|
||||
pointer_type = type_mgr->GetType(new_ptr_inst->type_id())->AsPointer();
|
||||
uint32_t new_type_id = type_mgr->GetId(pointer_type->pointee_type());
|
||||
if (new_type_id != use->type_id()) {
|
||||
use->SetResultType(new_type_id);
|
||||
UpdateUses(use, use);
|
||||
}
|
||||
} break;
|
||||
case SpvOpAccessChain: {
|
||||
// Update the actual use.
|
||||
use->SetOperand(index, {new_ptr_inst->result_id()});
|
||||
|
||||
// Update the result type.
|
||||
pointer_type = type_mgr->GetType(new_ptr_inst->type_id())->AsPointer();
|
||||
const analysis::Type* pointee_type = pointer_type->pointee_type();
|
||||
|
||||
// Convert the ids on the OpAccessChain to indices that can be used to
|
||||
// get the specific member.
|
||||
std::vector<uint32_t> access_chain;
|
||||
for (uint32_t i = 1; i < use->NumInOperands(); ++i) {
|
||||
const analysis::Constant* index_const =
|
||||
const_mgr->FindDeclaredConstant(use->GetSingleWordInOperand(i));
|
||||
if (index_const) {
|
||||
access_chain.push_back(index_const->AsIntConstant()->GetU32());
|
||||
} else {
|
||||
// Variable index means the type is an type where every element
|
||||
// is the same type. Use element 0 to get the type.
|
||||
access_chain.push_back(0);
|
||||
}
|
||||
}
|
||||
const analysis::Type* new_pointee_type =
|
||||
type_mgr->GetMemberType(pointee_type, access_chain);
|
||||
|
||||
// Now build a pointer to the type of the member.
|
||||
opt::analysis::Pointer new_pointer_type(new_pointee_type,
|
||||
pointer_type->storage_class());
|
||||
uint32_t new_pointer_type_id =
|
||||
context()->get_type_mgr()->GetTypeInstruction(&new_pointer_type);
|
||||
|
||||
if (new_pointer_type_id != use->type_id()) {
|
||||
use->SetResultType(new_pointer_type_id);
|
||||
UpdateUses(use, use);
|
||||
}
|
||||
} break;
|
||||
case SpvOpCompositeExtract: {
|
||||
// Update the actual use.
|
||||
use->SetOperand(index, {new_ptr_inst->result_id()});
|
||||
|
||||
std::vector<uint32_t> access_chain;
|
||||
for (uint32_t i = 1; i < use->NumInOperands(); ++i) {
|
||||
access_chain.push_back(use->GetSingleWordInOperand(i));
|
||||
}
|
||||
|
||||
const analysis::Type* type = type_mgr->GetType(new_ptr_inst->type_id());
|
||||
const analysis::Type* new_type =
|
||||
type_mgr->GetMemberType(type, access_chain);
|
||||
uint32_t new_type_id = type_mgr->GetTypeInstruction(new_type);
|
||||
|
||||
if (new_type_id != use->type_id()) {
|
||||
use->SetResultType(new_type_id);
|
||||
UpdateUses(use, use);
|
||||
}
|
||||
} break;
|
||||
case SpvOpStore:
|
||||
// If the use is the pointer, then it is the single store to that
|
||||
// variable. We do not want to replace it. Instead, it will become
|
||||
// dead after all of the loads are removed, and ADCE will get rid of it.
|
||||
//
|
||||
// If the use is the object being stored, we do not know how to change
|
||||
// the type, so we assume that |CanUpdateUse| would have returned false,
|
||||
// and we should not have called this function.
|
||||
assert(index != 1 && "Have to change the type of the stored object.");
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false && "Don't know how to rewrite instruction");
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CopyPropagateArrays::MemoryObject::GetMember(
|
||||
const std::vector<uint32_t>& access_chain) {
|
||||
access_chain_.insert(access_chain_.end(), access_chain.begin(),
|
||||
access_chain.end());
|
||||
}
|
||||
|
||||
uint32_t CopyPropagateArrays::MemoryObject::GetNumberOfMembers() {
|
||||
ir::IRContext* context = variable_inst_->context();
|
||||
analysis::TypeManager* type_mgr = context->get_type_mgr();
|
||||
|
||||
const analysis::Type* type = type_mgr->GetType(variable_inst_->type_id());
|
||||
type = type->AsPointer()->pointee_type();
|
||||
type = type_mgr->GetMemberType(type, access_chain_);
|
||||
|
||||
if (const analysis::Struct* struct_type = type->AsStruct()) {
|
||||
return static_cast<uint32_t>(struct_type->element_types().size());
|
||||
} else if (const analysis::Array* array_type = type->AsArray()) {
|
||||
const analysis::Constant* length_const =
|
||||
context->get_constant_mgr()->FindDeclaredConstant(
|
||||
array_type->LengthId());
|
||||
assert(length_const->AsIntConstant());
|
||||
return length_const->AsIntConstant()->GetU32();
|
||||
} else if (const analysis::Vector* vector_type = type->AsVector()) {
|
||||
return vector_type->element_count();
|
||||
} else if (const analysis::Matrix* matrix_type = type->AsMatrix()) {
|
||||
return matrix_type->element_count();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <class iterator>
|
||||
CopyPropagateArrays::MemoryObject::MemoryObject(ir::Instruction* var_inst,
|
||||
iterator begin, iterator end)
|
||||
: variable_inst_(var_inst), access_chain_(begin, end) {}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
193
source/opt/copy_prop_arrays.h
Normal file
193
source/opt/copy_prop_arrays.h
Normal file
@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2018 Google LLC.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef LIBSPIRV_OPT_COPY_PROP_H_
|
||||
#define LIBSPIRV_OPT_COPY_PROP_H_
|
||||
|
||||
#include "mem_pass.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
// This pass implements a simple array copy propagation. It does not do a full
|
||||
// array data flow. It looks for simple cases that meet the following
|
||||
// conditions:
|
||||
//
|
||||
// 1) The source must never be stored to.
|
||||
// 2) The target must be stored to exactly once.
|
||||
// 3) The store to the target must be a store to the entire array, and be a
|
||||
// copy of the entire source.
|
||||
// 4) All loads of the target must be dominated by the store.
|
||||
//
|
||||
// The hard part is keeping all of the types correct. We do not want to
|
||||
// have to do too large a search to update everything, which may not be
|
||||
// possible, do we give up if we see any instruction that might be hard to
|
||||
// update.
|
||||
|
||||
class CopyPropagateArrays : public MemPass {
|
||||
public:
|
||||
const char* name() const override { return "copy-propagate-arrays"; }
|
||||
Status Process(ir::IRContext*) override;
|
||||
|
||||
private:
|
||||
// The class used to identify a particular memory object. This memory object
|
||||
// will be owned by a particular variable, meaning that the memory is part of
|
||||
// that variable. It could be the entire variable or a member of the
|
||||
// variable.
|
||||
class MemoryObject {
|
||||
public:
|
||||
// Construction a memory object that is owned by |var_inst|. The iterator
|
||||
// |begin| and |end| traverse a container of integers that identify which
|
||||
// member of |var_inst| this memory object will represent. These integers
|
||||
// are interpreted the same way they would be in an |OpCompositeExtract|
|
||||
// instruction.
|
||||
template <class iterator>
|
||||
MemoryObject(ir::Instruction* var_inst, iterator begin, iterator end);
|
||||
|
||||
// Change |this| to now point to the member identified by |access_chain|
|
||||
// (starting from the current member). The elements in |access_chain| are
|
||||
// interpreted the same as the indicies in the |OpCompositeExtract|
|
||||
// instruction.
|
||||
void GetMember(const std::vector<uint32_t>& access_chain);
|
||||
|
||||
// Change |this| to now represent the first enclosing object to which it
|
||||
// belongs. (Remove the last element off the access_chain). It is invalid
|
||||
// to call this function if |this| does not represent a member of its owner.
|
||||
void GetParent() {
|
||||
assert(IsMember());
|
||||
access_chain_.pop_back();
|
||||
}
|
||||
|
||||
// Returns true if |this| represents a member of its owner, and not the
|
||||
// entire variable.
|
||||
bool IsMember() const { return !access_chain_.empty(); }
|
||||
|
||||
// Returns the number of members in the object represented by |this|. If
|
||||
// |this| does not represent a composite type, the return value will be 0.
|
||||
uint32_t GetNumberOfMembers();
|
||||
|
||||
// Returns the owning variable that the memory object is contained in.
|
||||
ir::Instruction* GetVariable() const { return variable_inst_; }
|
||||
|
||||
// Returns a vector of integers that can be used to access the specific
|
||||
// member that |this| represents starting from the owning variable. These
|
||||
// values are to be interpreted the same way the indicies are in an
|
||||
// |OpCompositeExtract| instruction.
|
||||
const std::vector<uint32_t>& AccessChain() const { return access_chain_; }
|
||||
|
||||
// Returns the type id of the pointer type that can be used to point to this
|
||||
// memory object.
|
||||
uint32_t GetPointerTypeId() const {
|
||||
analysis::TypeManager* type_mgr =
|
||||
GetVariable()->context()->get_type_mgr();
|
||||
const analysis::Pointer* pointer_type =
|
||||
type_mgr->GetType(GetVariable()->type_id())->AsPointer();
|
||||
const analysis::Type* var_type = pointer_type->pointee_type();
|
||||
const analysis::Type* member_type =
|
||||
type_mgr->GetMemberType(var_type, AccessChain());
|
||||
uint32_t member_type_id = type_mgr->GetId(member_type);
|
||||
assert(member_type != 0);
|
||||
uint32_t member_pointer_type_id = type_mgr->FindPointerToType(
|
||||
member_type_id, pointer_type->storage_class());
|
||||
return member_pointer_type_id;
|
||||
}
|
||||
|
||||
// Returns the storage class of the memory object.
|
||||
SpvStorageClass GetStorageClass() const {
|
||||
analysis::TypeManager* type_mgr =
|
||||
GetVariable()->context()->get_type_mgr();
|
||||
const analysis::Pointer* pointer_type =
|
||||
type_mgr->GetType(GetVariable()->type_id())->AsPointer();
|
||||
return pointer_type->storage_class();
|
||||
}
|
||||
|
||||
private:
|
||||
// The variable that owns this memory object.
|
||||
ir::Instruction* variable_inst_;
|
||||
|
||||
// The access chain to reach the particular member the memory object
|
||||
// represents. It should be interpreted the same way the indices in an
|
||||
// |OpCompositeExtract| are interpreted.
|
||||
std::vector<uint32_t> access_chain_;
|
||||
};
|
||||
|
||||
// Returns a memory object, if one exists, that can be used in place of
|
||||
// |var_inst| in all of the loads of |var_inst|. This code is conservative
|
||||
// and only identifies very simple cases. If no such memory object can be
|
||||
// found, the return value is |nullptr|.
|
||||
std::unique_ptr<MemoryObject> FindSourceObjectIfPossible(
|
||||
ir::Instruction* var_inst);
|
||||
|
||||
// Replaces all loads of |var_inst| with a load from |source| instead.
|
||||
void PropagateObject(ir::Instruction* var_inst, MemoryObject* source);
|
||||
|
||||
// Returns true if all of the references to |ptr_inst| can be rewritten and
|
||||
// are dominated by |store_inst|.
|
||||
bool HasValidReferencesOnly(ir::Instruction* ptr_inst,
|
||||
ir::Instruction* store_inst);
|
||||
|
||||
// Returns a memory object that at one time was equivalent to the value in
|
||||
// |result|. If no such memory object exists, the return value is |nullptr|.
|
||||
std::unique_ptr<MemoryObject> GetSourceObjectIfAny(uint32_t result);
|
||||
|
||||
// Returns the memory object that is loaded by |load_inst|. If a memory
|
||||
// object cannot be identified, the return value is |nullptr|. The opcode of
|
||||
// |load_inst| must be |OpLoad|.
|
||||
std::unique_ptr<MemoryObject> BuildMemoryObjectFromLoad(
|
||||
ir::Instruction* load_inst);
|
||||
|
||||
// Returns the memory object that at some point was equivalent to the result
|
||||
// of |extract_inst|. If a memory object cannot be identified, the return
|
||||
// value is |nullptr|. The opcode of |extract_inst| must be
|
||||
// |OpCompositeExtract|.
|
||||
std::unique_ptr<MemoryObject> BuildMemoryObjectFromExtract(
|
||||
ir::Instruction* extract_inst);
|
||||
|
||||
// Returns the memory object that at some point was equivalent to the result
|
||||
// of |construct_inst|. If a memory object cannot be identified, the return
|
||||
// value is |nullptr|. The opcode of |extract_inst| must be
|
||||
// |OpCompositeConstruct|.
|
||||
std::unique_ptr<MemoryObject> BuildMemoryObjectFromCompositeConstruct(
|
||||
ir::Instruction* conststruct_inst);
|
||||
|
||||
// Return true if |type_id| is a pointer type whose pointee type is an array.
|
||||
bool IsPointerToArrayType(uint32_t type_id);
|
||||
|
||||
// Returns true of there are not stores using |ptr_inst| or something derived
|
||||
// from it.
|
||||
bool HasNoStores(ir::Instruction* ptr_inst);
|
||||
|
||||
// Creates an |OpAccessChain| instruction whose result is a pointer the memory
|
||||
// represented by |source|. The new instruction will be placed before
|
||||
// |insertion_point|. |insertion_point| must be part of a function. Returns
|
||||
// the new instruction.
|
||||
ir::Instruction* BuildNewAccessChain(ir::Instruction* insertion_point,
|
||||
MemoryObject* source) const;
|
||||
|
||||
// Rewrites all uses of |original_ptr| to use |new_pointer_inst| updating
|
||||
// types of other instructions as needed. This function should not be called
|
||||
// if |CanUpdateUses(original_ptr_inst, new_pointer_inst->type_id())| returns
|
||||
// false.
|
||||
void UpdateUses(ir::Instruction* original_ptr_inst,
|
||||
ir::Instruction* new_pointer_inst);
|
||||
|
||||
// Return true if |UpdateUses| is able to change all of the uses of
|
||||
// |original_ptr_inst| to |type_id| and still have valid code.
|
||||
bool CanUpdateUses(ir::Instruction* original_ptr_inst, uint32_t type_id);
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // LIBSPIRV_OPT_COPY_PROP_H_
|
@ -16,6 +16,8 @@
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include "ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
@ -37,5 +39,35 @@ ir::BasicBlock* DominatorAnalysisBase::CommonDominator(
|
||||
return block;
|
||||
}
|
||||
|
||||
bool DominatorAnalysisBase::Dominates(ir::Instruction* a,
|
||||
ir::Instruction* b) const {
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ir::BasicBlock* bb_a = a->context()->get_instr_block(a);
|
||||
ir::BasicBlock* bb_b = b->context()->get_instr_block(b);
|
||||
|
||||
if (bb_a != bb_b) {
|
||||
return tree_.Dominates(bb_a, bb_b);
|
||||
}
|
||||
|
||||
for (ir::Instruction& inst : *bb_a) {
|
||||
if (&inst == a) {
|
||||
return true;
|
||||
} else if (&inst == b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
assert(false &&
|
||||
"We did not find the load or store in the block they are "
|
||||
"supposed to be in.");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
@ -47,6 +47,9 @@ class DominatorAnalysisBase {
|
||||
return tree_.Dominates(a, b);
|
||||
}
|
||||
|
||||
// Returns true if instruction |a| dominates instruction |b|.
|
||||
bool Dominates(ir::Instruction* a, ir::Instruction* b) const;
|
||||
|
||||
// Returns true if BasicBlock |a| strictly dominates BasicBlock |b|.
|
||||
inline bool StrictlyDominates(const ir::BasicBlock* a,
|
||||
const ir::BasicBlock* b) const {
|
||||
|
@ -316,6 +316,21 @@ class InstructionBuilder {
|
||||
return AddInstruction(std::move(select));
|
||||
}
|
||||
|
||||
ir::Instruction* AddAccessChain(uint32_t type_id, uint32_t base_ptr_id,
|
||||
std::vector<uint32_t> ids) {
|
||||
std::vector<ir::Operand> operands;
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {base_ptr_id}});
|
||||
|
||||
for (uint32_t index_id : ids) {
|
||||
operands.push_back({SPV_OPERAND_TYPE_ID, {index_id}});
|
||||
}
|
||||
|
||||
std::unique_ptr<ir::Instruction> new_inst(
|
||||
new ir::Instruction(GetContext(), SpvOpAccessChain, type_id,
|
||||
GetContext()->TakeNextId(), operands));
|
||||
return AddInstruction(std::move(new_inst));
|
||||
}
|
||||
|
||||
// Inserts the new instruction before the insertion point.
|
||||
ir::Instruction* AddInstruction(std::unique_ptr<ir::Instruction>&& insn) {
|
||||
ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
|
||||
|
@ -120,6 +120,8 @@ Optimizer& Optimizer::RegisterLegalizationPasses() {
|
||||
// scalar replacement. Also important for removing OpPhi nodes.
|
||||
.RegisterPass(CreateSimplificationPass())
|
||||
.RegisterPass(CreateInsertExtractElimPass())
|
||||
.RegisterPass(CreateAggressiveDCEPass())
|
||||
.RegisterPass(CreateCopyPropagateArraysPass())
|
||||
// May need loop unrolling here see
|
||||
// https://github.com/Microsoft/DirectXShaderCompiler/pull/930
|
||||
// Get rid of unused code that contain traces of illegal code
|
||||
@ -437,4 +439,9 @@ Optimizer::PassToken CreateSSARewritePass() {
|
||||
MakeUnique<opt::SSARewritePass>());
|
||||
}
|
||||
|
||||
Optimizer::PassToken CreateCopyPropagateArraysPass() {
|
||||
return MakeUnique<Optimizer::PassToken::Impl>(
|
||||
MakeUnique<opt::CopyPropagateArrays>());
|
||||
}
|
||||
|
||||
} // namespace spvtools
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "cfg_cleanup_pass.h"
|
||||
#include "common_uniform_elim_pass.h"
|
||||
#include "compact_ids_pass.h"
|
||||
#include "copy_prop_arrays.h"
|
||||
#include "dead_branch_elim_pass.h"
|
||||
#include "dead_insert_elim_pass.h"
|
||||
#include "dead_variable_elimination.h"
|
||||
|
@ -669,6 +669,24 @@ void TypeManager::AttachDecoration(const ir::Instruction& inst, Type* type) {
|
||||
}
|
||||
}
|
||||
|
||||
const Type* TypeManager::GetMemberType(
|
||||
const Type* parent_type, const std::vector<uint32_t>& access_chain) {
|
||||
for (uint32_t element_index : access_chain) {
|
||||
if (const analysis::Struct* struct_type = parent_type->AsStruct()) {
|
||||
parent_type = struct_type->element_types()[element_index];
|
||||
} else if (const analysis::Array* array_type = parent_type->AsArray()) {
|
||||
parent_type = array_type->element_type();
|
||||
} else if (const analysis::Vector* vector_type = parent_type->AsVector()) {
|
||||
parent_type = vector_type->element_type();
|
||||
} else if (const analysis::Matrix* matrix_type = parent_type->AsMatrix()) {
|
||||
parent_type = matrix_type->element_type();
|
||||
} else {
|
||||
assert(false && "Trying to get a member of a type without members.");
|
||||
}
|
||||
}
|
||||
return parent_type;
|
||||
}
|
||||
|
||||
} // namespace analysis
|
||||
} // namespace opt
|
||||
} // namespace spvtools
|
||||
|
@ -132,6 +132,14 @@ class TypeManager {
|
||||
// defining that type.
|
||||
void RemoveId(uint32_t id);
|
||||
|
||||
// Returns the type of the member of |parent_type| that is identified by
|
||||
// |access_chain|. The vector |access_chain| is a series of integers that are
|
||||
// used to pick members as in the |OpCompositeExtract| instructions. If you
|
||||
// want a member of an array, vector, or matrix that does not have a constant
|
||||
// index, you can use 0 in that position. All elements have the same type.
|
||||
const Type* GetMemberType(const Type* parent_type,
|
||||
const std::vector<uint32_t>& access_chain);
|
||||
|
||||
private:
|
||||
using TypeToIdMap = std::unordered_map<const Type*, uint32_t, HashTypePointer,
|
||||
CompareTypePointers>;
|
||||
|
@ -486,7 +486,7 @@ void Opaque::GetExtraHashWords(std::vector<uint32_t>* words) const {
|
||||
}
|
||||
}
|
||||
|
||||
Pointer::Pointer(Type* type, SpvStorageClass sc)
|
||||
Pointer::Pointer(const Type* type, SpvStorageClass sc)
|
||||
: Type(kPointer), pointee_type_(type), storage_class_(sc) {}
|
||||
|
||||
bool Pointer::IsSame(const Type* that) const {
|
||||
|
@ -17,6 +17,7 @@
|
||||
#ifndef LIBSPIRV_OPT_TYPES_H_
|
||||
#define LIBSPIRV_OPT_TYPES_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@ -369,7 +370,8 @@ class Struct : public Type {
|
||||
bool decoration_empty() const override {
|
||||
return decorations_.empty() && element_decorations_.empty();
|
||||
}
|
||||
const std::unordered_map<uint32_t, std::vector<std::vector<uint32_t>>>&
|
||||
|
||||
const std::map<uint32_t, std::vector<std::vector<uint32_t>>>&
|
||||
element_decorations() const {
|
||||
return element_decorations_;
|
||||
}
|
||||
@ -388,9 +390,10 @@ class Struct : public Type {
|
||||
std::vector<Type*> element_types_;
|
||||
// We can attach decorations to struct members and that should not affect the
|
||||
// underlying element type. So we need an extra data structure here to keep
|
||||
// track of element type decorations.
|
||||
std::unordered_map<uint32_t, std::vector<std::vector<uint32_t>>>
|
||||
element_decorations_;
|
||||
// track of element type decorations. They must be stored in an ordered map
|
||||
// because |GetExtraHashWords| will traverse the structure. It must have a
|
||||
// fixed order in order to hash to the same value every time.
|
||||
std::map<uint32_t, std::vector<std::vector<uint32_t>>> element_decorations_;
|
||||
};
|
||||
|
||||
class Opaque : public Type {
|
||||
@ -414,7 +417,7 @@ class Opaque : public Type {
|
||||
|
||||
class Pointer : public Type {
|
||||
public:
|
||||
Pointer(Type* pointee, SpvStorageClass sc);
|
||||
Pointer(const Type* pointee, SpvStorageClass sc);
|
||||
Pointer(const Pointer&) = default;
|
||||
|
||||
bool IsSame(const Type* that) const override;
|
||||
@ -428,7 +431,7 @@ class Pointer : public Type {
|
||||
void GetExtraHashWords(std::vector<uint32_t>* words) const override;
|
||||
|
||||
private:
|
||||
Type* pointee_type_;
|
||||
const Type* pointee_type_;
|
||||
SpvStorageClass storage_class_;
|
||||
};
|
||||
|
||||
|
@ -301,3 +301,8 @@ add_spvtools_unittest(TARGET simplification
|
||||
SRCS simplification_test.cpp pass_utils.cpp
|
||||
LIBS SPIRV-Tools-opt
|
||||
)
|
||||
|
||||
add_spvtools_unittest(TARGET copy_prop_array
|
||||
SRCS copy_prop_array_test.cpp pass_utils.cpp
|
||||
LIBS SPIRV-Tools-opt
|
||||
)
|
||||
|
602
test/opt/copy_prop_array_test.cpp
Normal file
602
test/opt/copy_prop_array_test.cpp
Normal file
@ -0,0 +1,602 @@
|
||||
// Copyright (c) 2018 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "assembly_builder.h"
|
||||
#include "pass_fixture.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using namespace spvtools;
|
||||
using ir::IRContext;
|
||||
using ir::Instruction;
|
||||
using opt::PassManager;
|
||||
|
||||
using CopyPropArrayPassTest = PassTest<::testing::Test>;
|
||||
|
||||
#ifdef SPIRV_EFFCEE
|
||||
TEST_F(CopyPropArrayPassTest, BasicPropagateArray) {
|
||||
const std::string before =
|
||||
R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpName %type_MyCBuffer "type.MyCBuffer"
|
||||
OpMemberName %type_MyCBuffer 0 "Data"
|
||||
OpName %MyCBuffer "MyCBuffer"
|
||||
OpName %main "main"
|
||||
OpName %in_var_INDEX "in.var.INDEX"
|
||||
OpName %out_var_SV_Target "out.var.SV_Target"
|
||||
OpDecorate %_arr_v4float_uint_8 ArrayStride 16
|
||||
OpMemberDecorate %type_MyCBuffer 0 Offset 0
|
||||
OpDecorate %type_MyCBuffer Block
|
||||
OpDecorate %in_var_INDEX Flat
|
||||
OpDecorate %in_var_INDEX Location 0
|
||||
OpDecorate %out_var_SV_Target Location 0
|
||||
OpDecorate %MyCBuffer DescriptorSet 0
|
||||
OpDecorate %MyCBuffer Binding 0
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_8 = OpConstant %uint 8
|
||||
%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
|
||||
%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
|
||||
%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
|
||||
%void = OpTypeVoid
|
||||
%13 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
|
||||
%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
|
||||
%int_0 = OpConstant %int 0
|
||||
%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
|
||||
%_ptr_Function_v4float = OpTypePointer Function %v4float
|
||||
%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
|
||||
%in_var_INDEX = OpVariable %_ptr_Input_int Input
|
||||
%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
|
||||
; CHECK: OpFunction
|
||||
; CHECK: OpLabel
|
||||
; CHECK: OpVariable
|
||||
; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %uint_0
|
||||
%main = OpFunction %void None %13
|
||||
%22 = OpLabel
|
||||
%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
|
||||
%24 = OpLoad %int %in_var_INDEX
|
||||
%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
|
||||
%26 = OpLoad %_arr_v4float_uint_8 %25
|
||||
%27 = OpCompositeExtract %v4float %26 0
|
||||
%28 = OpCompositeExtract %v4float %26 1
|
||||
%29 = OpCompositeExtract %v4float %26 2
|
||||
%30 = OpCompositeExtract %v4float %26 3
|
||||
%31 = OpCompositeExtract %v4float %26 4
|
||||
%32 = OpCompositeExtract %v4float %26 5
|
||||
%33 = OpCompositeExtract %v4float %26 6
|
||||
%34 = OpCompositeExtract %v4float %26 7
|
||||
%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
|
||||
OpStore %23 %35
|
||||
%36 = OpAccessChain %_ptr_Function_v4float %23 %24
|
||||
; CHECK %37 = OpLoad %v4float [[new_address]]
|
||||
%37 = OpLoad %v4float %36
|
||||
OpStore %out_var_SV_Target %37
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
SinglePassRunAndMatch<opt::CopyPropagateArrays>(before, false);
|
||||
}
|
||||
|
||||
// Propagate 2d array. This test identifing a copy through multiple levels.
|
||||
// Also has to traverse multiple OpAccessChains.
|
||||
TEST_F(CopyPropArrayPassTest, Propagate2DArray) {
|
||||
const std::string text =
|
||||
R"(OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpName %type_MyCBuffer "type.MyCBuffer"
|
||||
OpMemberName %type_MyCBuffer 0 "Data"
|
||||
OpName %MyCBuffer "MyCBuffer"
|
||||
OpName %main "main"
|
||||
OpName %in_var_INDEX "in.var.INDEX"
|
||||
OpName %out_var_SV_Target "out.var.SV_Target"
|
||||
OpDecorate %_arr_v4float_uint_2 ArrayStride 16
|
||||
OpDecorate %_arr__arr_v4float_uint_2_uint_2 ArrayStride 32
|
||||
OpMemberDecorate %type_MyCBuffer 0 Offset 0
|
||||
OpDecorate %type_MyCBuffer Block
|
||||
OpDecorate %in_var_INDEX Flat
|
||||
OpDecorate %in_var_INDEX Location 0
|
||||
OpDecorate %out_var_SV_Target Location 0
|
||||
OpDecorate %MyCBuffer DescriptorSet 0
|
||||
OpDecorate %MyCBuffer Binding 0
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_2 = OpConstant %uint 2
|
||||
%_arr_v4float_uint_2 = OpTypeArray %v4float %uint_2
|
||||
%_arr__arr_v4float_uint_2_uint_2 = OpTypeArray %_arr_v4float_uint_2 %uint_2
|
||||
%type_MyCBuffer = OpTypeStruct %_arr__arr_v4float_uint_2_uint_2
|
||||
%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
|
||||
%void = OpTypeVoid
|
||||
%14 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%_arr_v4float_uint_2_0 = OpTypeArray %v4float %uint_2
|
||||
%_arr__arr_v4float_uint_2_0_uint_2 = OpTypeArray %_arr_v4float_uint_2_0 %uint_2
|
||||
%_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 = OpTypePointer Function %_arr__arr_v4float_uint_2_0_uint_2
|
||||
%int_0 = OpConstant %int 0
|
||||
%_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 = OpTypePointer Uniform %_arr__arr_v4float_uint_2_uint_2
|
||||
%_ptr_Function__arr_v4float_uint_2_0 = OpTypePointer Function %_arr_v4float_uint_2_0
|
||||
%_ptr_Function_v4float = OpTypePointer Function %v4float
|
||||
%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
|
||||
%in_var_INDEX = OpVariable %_ptr_Input_int Input
|
||||
%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
|
||||
; CHECK: OpFunction
|
||||
; CHECK: OpLabel
|
||||
; CHECK: OpVariable
|
||||
; CHECK: OpVariable
|
||||
; CHECK: [[new_address:%\w+]] = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %uint_0
|
||||
%main = OpFunction %void None %14
|
||||
%25 = OpLabel
|
||||
%26 = OpVariable %_ptr_Function__arr_v4float_uint_2_0 Function
|
||||
%27 = OpVariable %_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 Function
|
||||
%28 = OpLoad %int %in_var_INDEX
|
||||
%29 = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0
|
||||
%30 = OpLoad %_arr__arr_v4float_uint_2_uint_2 %29
|
||||
%31 = OpCompositeExtract %_arr_v4float_uint_2 %30 0
|
||||
%32 = OpCompositeExtract %v4float %31 0
|
||||
%33 = OpCompositeExtract %v4float %31 1
|
||||
%34 = OpCompositeConstruct %_arr_v4float_uint_2_0 %32 %33
|
||||
%35 = OpCompositeExtract %_arr_v4float_uint_2 %30 1
|
||||
%36 = OpCompositeExtract %v4float %35 0
|
||||
%37 = OpCompositeExtract %v4float %35 1
|
||||
%38 = OpCompositeConstruct %_arr_v4float_uint_2_0 %36 %37
|
||||
%39 = OpCompositeConstruct %_arr__arr_v4float_uint_2_0_uint_2 %34 %38
|
||||
; CHECK: OpStore
|
||||
; CHECK: [[ac1:%\w+]] = OpAccessChain %_ptr_Uniform__arr_v4float_uint_2 [[new_address]] %28
|
||||
; CHECK: [[ac2:%\w+]] = OpAccessChain %_ptr_Uniform_v4float [[ac1]] %28
|
||||
; CHECK: OpLoad %v4float [[ac2]]
|
||||
OpStore %27 %39
|
||||
%40 = OpAccessChain %_ptr_Function__arr_v4float_uint_2_0 %27 %28
|
||||
%42 = OpAccessChain %_ptr_Function_v4float %40 %28
|
||||
%43 = OpLoad %v4float %42
|
||||
OpStore %out_var_SV_Target %43
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
SinglePassRunAndMatch<opt::CopyPropagateArrays>(text, false);
|
||||
}
|
||||
#endif // SPIRV_EFFCEE
|
||||
|
||||
// This test will place a load before the store. We cannot propagate in this
|
||||
// case.
|
||||
TEST_F(CopyPropArrayPassTest, LoadBeforeStore) {
|
||||
const std::string text =
|
||||
R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpName %type_MyCBuffer "type.MyCBuffer"
|
||||
OpMemberName %type_MyCBuffer 0 "Data"
|
||||
OpName %MyCBuffer "MyCBuffer"
|
||||
OpName %main "main"
|
||||
OpName %in_var_INDEX "in.var.INDEX"
|
||||
OpName %out_var_SV_Target "out.var.SV_Target"
|
||||
OpDecorate %_arr_v4float_uint_8 ArrayStride 16
|
||||
OpMemberDecorate %type_MyCBuffer 0 Offset 0
|
||||
OpDecorate %type_MyCBuffer Block
|
||||
OpDecorate %in_var_INDEX Flat
|
||||
OpDecorate %in_var_INDEX Location 0
|
||||
OpDecorate %out_var_SV_Target Location 0
|
||||
OpDecorate %MyCBuffer DescriptorSet 0
|
||||
OpDecorate %MyCBuffer Binding 0
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_8 = OpConstant %uint 8
|
||||
%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
|
||||
%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
|
||||
%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
|
||||
%void = OpTypeVoid
|
||||
%13 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
|
||||
%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
|
||||
%int_0 = OpConstant %int 0
|
||||
%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
|
||||
%_ptr_Function_v4float = OpTypePointer Function %v4float
|
||||
%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
|
||||
%in_var_INDEX = OpVariable %_ptr_Input_int Input
|
||||
%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
|
||||
%main = OpFunction %void None %13
|
||||
%22 = OpLabel
|
||||
%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
|
||||
%38 = OpAccessChain %_ptr_Function_v4float %23 %24
|
||||
%39 = OpLoad %v4float %36
|
||||
%24 = OpLoad %int %in_var_INDEX
|
||||
%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
|
||||
%26 = OpLoad %_arr_v4float_uint_8 %25
|
||||
%27 = OpCompositeExtract %v4float %26 0
|
||||
%28 = OpCompositeExtract %v4float %26 1
|
||||
%29 = OpCompositeExtract %v4float %26 2
|
||||
%30 = OpCompositeExtract %v4float %26 3
|
||||
%31 = OpCompositeExtract %v4float %26 4
|
||||
%32 = OpCompositeExtract %v4float %26 5
|
||||
%33 = OpCompositeExtract %v4float %26 6
|
||||
%34 = OpCompositeExtract %v4float %26 7
|
||||
%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
|
||||
OpStore %23 %35
|
||||
%36 = OpAccessChain %_ptr_Function_v4float %23 %24
|
||||
%37 = OpLoad %v4float %36
|
||||
OpStore %out_var_SV_Target %37
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
auto result = SinglePassRunAndDisassemble<opt::CopyPropagateArrays>(
|
||||
text, /* skip_nop = */ true, /* do_validation = */ false);
|
||||
|
||||
EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
|
||||
}
|
||||
|
||||
// This test will place a load where it is not dominated by the store. We
|
||||
// cannot propagate in this case.
|
||||
TEST_F(CopyPropArrayPassTest, LoadNotDominated) {
|
||||
const std::string text =
|
||||
R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpName %type_MyCBuffer "type.MyCBuffer"
|
||||
OpMemberName %type_MyCBuffer 0 "Data"
|
||||
OpName %MyCBuffer "MyCBuffer"
|
||||
OpName %main "main"
|
||||
OpName %in_var_INDEX "in.var.INDEX"
|
||||
OpName %out_var_SV_Target "out.var.SV_Target"
|
||||
OpDecorate %_arr_v4float_uint_8 ArrayStride 16
|
||||
OpMemberDecorate %type_MyCBuffer 0 Offset 0
|
||||
OpDecorate %type_MyCBuffer Block
|
||||
OpDecorate %in_var_INDEX Flat
|
||||
OpDecorate %in_var_INDEX Location 0
|
||||
OpDecorate %out_var_SV_Target Location 0
|
||||
OpDecorate %MyCBuffer DescriptorSet 0
|
||||
OpDecorate %MyCBuffer Binding 0
|
||||
%bool = OpTypeBool
|
||||
%true = OpConstantTrue %bool
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_8 = OpConstant %uint 8
|
||||
%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
|
||||
%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
|
||||
%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
|
||||
%void = OpTypeVoid
|
||||
%13 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
|
||||
%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
|
||||
%int_0 = OpConstant %int 0
|
||||
%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
|
||||
%_ptr_Function_v4float = OpTypePointer Function %v4float
|
||||
%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
|
||||
%in_var_INDEX = OpVariable %_ptr_Input_int Input
|
||||
%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
|
||||
%main = OpFunction %void None %13
|
||||
%22 = OpLabel
|
||||
%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
|
||||
OpSelectionMerge %merge None
|
||||
OpBranchConditional %true %if %else
|
||||
%if = OpLabel
|
||||
%24 = OpLoad %int %in_var_INDEX
|
||||
%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
|
||||
%26 = OpLoad %_arr_v4float_uint_8 %25
|
||||
%27 = OpCompositeExtract %v4float %26 0
|
||||
%28 = OpCompositeExtract %v4float %26 1
|
||||
%29 = OpCompositeExtract %v4float %26 2
|
||||
%30 = OpCompositeExtract %v4float %26 3
|
||||
%31 = OpCompositeExtract %v4float %26 4
|
||||
%32 = OpCompositeExtract %v4float %26 5
|
||||
%33 = OpCompositeExtract %v4float %26 6
|
||||
%34 = OpCompositeExtract %v4float %26 7
|
||||
%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
|
||||
OpStore %23 %35
|
||||
%38 = OpAccessChain %_ptr_Function_v4float %23 %24
|
||||
%39 = OpLoad %v4float %36
|
||||
OpBranch %merge
|
||||
%else = OpLabel
|
||||
%36 = OpAccessChain %_ptr_Function_v4float %23 %24
|
||||
%37 = OpLoad %v4float %36
|
||||
OpBranch %merge
|
||||
%merge = OpLabel
|
||||
%phi = OpPhi %out_var_SV_Target %39 %if %37 %else
|
||||
OpStore %out_var_SV_Target %phi
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
auto result = SinglePassRunAndDisassemble<opt::CopyPropagateArrays>(
|
||||
text, /* skip_nop = */ true, /* do_validation = */ false);
|
||||
|
||||
EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
|
||||
}
|
||||
|
||||
// This test has a partial store to the variable. We cannot propagate in this
|
||||
// case.
|
||||
TEST_F(CopyPropArrayPassTest, PartialStore) {
|
||||
const std::string text =
|
||||
R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpName %type_MyCBuffer "type.MyCBuffer"
|
||||
OpMemberName %type_MyCBuffer 0 "Data"
|
||||
OpName %MyCBuffer "MyCBuffer"
|
||||
OpName %main "main"
|
||||
OpName %in_var_INDEX "in.var.INDEX"
|
||||
OpName %out_var_SV_Target "out.var.SV_Target"
|
||||
OpDecorate %_arr_v4float_uint_8 ArrayStride 16
|
||||
OpMemberDecorate %type_MyCBuffer 0 Offset 0
|
||||
OpDecorate %type_MyCBuffer Block
|
||||
OpDecorate %in_var_INDEX Flat
|
||||
OpDecorate %in_var_INDEX Location 0
|
||||
OpDecorate %out_var_SV_Target Location 0
|
||||
OpDecorate %MyCBuffer DescriptorSet 0
|
||||
OpDecorate %MyCBuffer Binding 0
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_8 = OpConstant %uint 8
|
||||
%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
|
||||
%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
|
||||
%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
|
||||
%void = OpTypeVoid
|
||||
%13 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
|
||||
%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
|
||||
%int_0 = OpConstant %int 0
|
||||
%f0 = OpConstant %float 0
|
||||
%v4const = OpConstantComposite %v4float %f0 %f0 %f0 %f0
|
||||
%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
|
||||
%_ptr_Function_v4float = OpTypePointer Function %v4float
|
||||
%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
|
||||
%in_var_INDEX = OpVariable %_ptr_Input_int Input
|
||||
%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
|
||||
%main = OpFunction %void None %13
|
||||
%22 = OpLabel
|
||||
%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
|
||||
%24 = OpLoad %int %in_var_INDEX
|
||||
%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
|
||||
%26 = OpLoad %_arr_v4float_uint_8 %25
|
||||
%27 = OpCompositeExtract %v4float %26 0
|
||||
%28 = OpCompositeExtract %v4float %26 1
|
||||
%29 = OpCompositeExtract %v4float %26 2
|
||||
%30 = OpCompositeExtract %v4float %26 3
|
||||
%31 = OpCompositeExtract %v4float %26 4
|
||||
%32 = OpCompositeExtract %v4float %26 5
|
||||
%33 = OpCompositeExtract %v4float %26 6
|
||||
%34 = OpCompositeExtract %v4float %26 7
|
||||
%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
|
||||
OpStore %23 %35
|
||||
%36 = OpAccessChain %_ptr_Function_v4float %23 %24
|
||||
%37 = OpLoad %v4float %36
|
||||
%39 = OpStore %36 %v4const
|
||||
OpStore %out_var_SV_Target %37
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
auto result = SinglePassRunAndDisassemble<opt::CopyPropagateArrays>(
|
||||
text, /* skip_nop = */ true, /* do_validation = */ false);
|
||||
|
||||
EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
|
||||
}
|
||||
|
||||
// This test does not have a proper copy of an object. We cannot propagate in
|
||||
// this case.
|
||||
TEST_F(CopyPropArrayPassTest, NotACopy) {
|
||||
const std::string text =
|
||||
R"(
|
||||
OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpName %type_MyCBuffer "type.MyCBuffer"
|
||||
OpMemberName %type_MyCBuffer 0 "Data"
|
||||
OpName %MyCBuffer "MyCBuffer"
|
||||
OpName %main "main"
|
||||
OpName %in_var_INDEX "in.var.INDEX"
|
||||
OpName %out_var_SV_Target "out.var.SV_Target"
|
||||
OpDecorate %_arr_v4float_uint_8 ArrayStride 16
|
||||
OpMemberDecorate %type_MyCBuffer 0 Offset 0
|
||||
OpDecorate %type_MyCBuffer Block
|
||||
OpDecorate %in_var_INDEX Flat
|
||||
OpDecorate %in_var_INDEX Location 0
|
||||
OpDecorate %out_var_SV_Target Location 0
|
||||
OpDecorate %MyCBuffer DescriptorSet 0
|
||||
OpDecorate %MyCBuffer Binding 0
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_8 = OpConstant %uint 8
|
||||
%_arr_v4float_uint_8 = OpTypeArray %v4float %uint_8
|
||||
%type_MyCBuffer = OpTypeStruct %_arr_v4float_uint_8
|
||||
%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
|
||||
%void = OpTypeVoid
|
||||
%13 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%_arr_v4float_uint_8_0 = OpTypeArray %v4float %uint_8
|
||||
%_ptr_Function__arr_v4float_uint_8_0 = OpTypePointer Function %_arr_v4float_uint_8_0
|
||||
%int_0 = OpConstant %int 0
|
||||
%f0 = OpConstant %float 0
|
||||
%v4const = OpConstantComposite %v4float %f0 %f0 %f0 %f0
|
||||
%_ptr_Uniform__arr_v4float_uint_8 = OpTypePointer Uniform %_arr_v4float_uint_8
|
||||
%_ptr_Function_v4float = OpTypePointer Function %v4float
|
||||
%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
|
||||
%in_var_INDEX = OpVariable %_ptr_Input_int Input
|
||||
%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
|
||||
%main = OpFunction %void None %13
|
||||
%22 = OpLabel
|
||||
%23 = OpVariable %_ptr_Function__arr_v4float_uint_8_0 Function
|
||||
%24 = OpLoad %int %in_var_INDEX
|
||||
%25 = OpAccessChain %_ptr_Uniform__arr_v4float_uint_8 %MyCBuffer %int_0
|
||||
%26 = OpLoad %_arr_v4float_uint_8 %25
|
||||
%27 = OpCompositeExtract %v4float %26 0
|
||||
%28 = OpCompositeExtract %v4float %26 0
|
||||
%29 = OpCompositeExtract %v4float %26 2
|
||||
%30 = OpCompositeExtract %v4float %26 3
|
||||
%31 = OpCompositeExtract %v4float %26 4
|
||||
%32 = OpCompositeExtract %v4float %26 5
|
||||
%33 = OpCompositeExtract %v4float %26 6
|
||||
%34 = OpCompositeExtract %v4float %26 7
|
||||
%35 = OpCompositeConstruct %_arr_v4float_uint_8_0 %27 %28 %29 %30 %31 %32 %33 %34
|
||||
OpStore %23 %35
|
||||
%36 = OpAccessChain %_ptr_Function_v4float %23 %24
|
||||
%37 = OpLoad %v4float %36
|
||||
OpStore %out_var_SV_Target %37
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
auto result = SinglePassRunAndDisassemble<opt::CopyPropagateArrays>(
|
||||
text, /* skip_nop = */ true, /* do_validation = */ false);
|
||||
|
||||
EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
|
||||
}
|
||||
|
||||
// This test is okay except that we would have to change type of the store
|
||||
// "OpStore %26 %41". We don't handle this yet.
|
||||
TEST_F(CopyPropArrayPassTest, CantRewriteStore) {
|
||||
const std::string text =
|
||||
R"( OpCapability Shader
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main" %in_var_INDEX %out_var_SV_Target
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource HLSL 600
|
||||
OpName %type_MyCBuffer "type.MyCBuffer"
|
||||
OpMemberName %type_MyCBuffer 0 "Data"
|
||||
OpName %MyCBuffer "MyCBuffer"
|
||||
OpName %main "main"
|
||||
OpName %in_var_INDEX "in.var.INDEX"
|
||||
OpName %out_var_SV_Target "out.var.SV_Target"
|
||||
OpDecorate %_arr_v4float_uint_2 ArrayStride 16
|
||||
OpDecorate %_arr__arr_v4float_uint_2_uint_2 ArrayStride 32
|
||||
OpMemberDecorate %type_MyCBuffer 0 Offset 0
|
||||
OpDecorate %type_MyCBuffer Block
|
||||
OpDecorate %in_var_INDEX Flat
|
||||
OpDecorate %in_var_INDEX Location 0
|
||||
OpDecorate %out_var_SV_Target Location 0
|
||||
OpDecorate %MyCBuffer DescriptorSet 0
|
||||
OpDecorate %MyCBuffer Binding 0
|
||||
%float = OpTypeFloat 32
|
||||
%v4float = OpTypeVector %float 4
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_2 = OpConstant %uint 2
|
||||
%_arr_v4float_uint_2 = OpTypeArray %v4float %uint_2
|
||||
%_arr__arr_v4float_uint_2_uint_2 = OpTypeArray %_arr_v4float_uint_2 %uint_2
|
||||
%type_MyCBuffer = OpTypeStruct %_arr__arr_v4float_uint_2_uint_2
|
||||
%_ptr_Uniform_type_MyCBuffer = OpTypePointer Uniform %type_MyCBuffer
|
||||
%void = OpTypeVoid
|
||||
%14 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Input_int = OpTypePointer Input %int
|
||||
%_ptr_Output_v4float = OpTypePointer Output %v4float
|
||||
%_arr_v4float_uint_2_0 = OpTypeArray %v4float %uint_2
|
||||
%_arr__arr_v4float_uint_2_0_uint_2 = OpTypeArray %_arr_v4float_uint_2_0 %uint_2
|
||||
%_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 = OpTypePointer Function %_arr__arr_v4float_uint_2_0_uint_2
|
||||
%int_0 = OpConstant %int 0
|
||||
%_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 = OpTypePointer Uniform %_arr__arr_v4float_uint_2_uint_2
|
||||
%_ptr_Function__arr_v4float_uint_2_0 = OpTypePointer Function %_arr_v4float_uint_2_0
|
||||
%_ptr_Function_v4float = OpTypePointer Function %v4float
|
||||
%MyCBuffer = OpVariable %_ptr_Uniform_type_MyCBuffer Uniform
|
||||
%in_var_INDEX = OpVariable %_ptr_Input_int Input
|
||||
%out_var_SV_Target = OpVariable %_ptr_Output_v4float Output
|
||||
%main = OpFunction %void None %14
|
||||
%25 = OpLabel
|
||||
%26 = OpVariable %_ptr_Function__arr_v4float_uint_2_0 Function
|
||||
%27 = OpVariable %_ptr_Function__arr__arr_v4float_uint_2_0_uint_2 Function
|
||||
%28 = OpLoad %int %in_var_INDEX
|
||||
%29 = OpAccessChain %_ptr_Uniform__arr__arr_v4float_uint_2_uint_2 %MyCBuffer %int_0
|
||||
%30 = OpLoad %_arr__arr_v4float_uint_2_uint_2 %29
|
||||
%31 = OpCompositeExtract %_arr_v4float_uint_2 %30 0
|
||||
%32 = OpCompositeExtract %v4float %31 0
|
||||
%33 = OpCompositeExtract %v4float %31 1
|
||||
%34 = OpCompositeConstruct %_arr_v4float_uint_2_0 %32 %33
|
||||
%35 = OpCompositeExtract %_arr_v4float_uint_2 %30 1
|
||||
%36 = OpCompositeExtract %v4float %35 0
|
||||
%37 = OpCompositeExtract %v4float %35 1
|
||||
%38 = OpCompositeConstruct %_arr_v4float_uint_2_0 %36 %37
|
||||
%39 = OpCompositeConstruct %_arr__arr_v4float_uint_2_0_uint_2 %34 %38
|
||||
OpStore %27 %39
|
||||
%40 = OpAccessChain %_ptr_Function__arr_v4float_uint_2_0 %27 %28
|
||||
%41 = OpLoad %_arr_v4float_uint_2_0 %40
|
||||
OpStore %26 %41
|
||||
%42 = OpAccessChain %_ptr_Function_v4float %26 %28
|
||||
%43 = OpLoad %v4float %42
|
||||
OpStore %out_var_SV_Target %43
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER |
|
||||
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES);
|
||||
auto result = SinglePassRunAndDisassemble<opt::CopyPropagateArrays>(
|
||||
text, /* skip_nop = */ true, /* do_validation = */ false);
|
||||
|
||||
EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
|
||||
}
|
||||
|
||||
} // namespace
|
@ -155,7 +155,8 @@ TEST_F(PassClassTest, DominatorSimpleCFG) {
|
||||
// check with some invalid inputs
|
||||
EXPECT_FALSE(dom_tree.Dominates(nullptr, entry));
|
||||
EXPECT_FALSE(dom_tree.Dominates(entry, nullptr));
|
||||
EXPECT_FALSE(dom_tree.Dominates(nullptr, nullptr));
|
||||
EXPECT_FALSE(dom_tree.Dominates(static_cast<ir::BasicBlock*>(nullptr),
|
||||
static_cast<ir::BasicBlock*>(nullptr)));
|
||||
EXPECT_FALSE(dom_tree.Dominates(10, 1));
|
||||
EXPECT_FALSE(dom_tree.Dominates(1, 10));
|
||||
EXPECT_FALSE(dom_tree.Dominates(1, 1));
|
||||
@ -216,7 +217,8 @@ TEST_F(PassClassTest, DominatorSimpleCFG) {
|
||||
// check with some invalid inputs
|
||||
EXPECT_FALSE(dom_tree.Dominates(nullptr, entry));
|
||||
EXPECT_FALSE(dom_tree.Dominates(entry, nullptr));
|
||||
EXPECT_FALSE(dom_tree.Dominates(nullptr, nullptr));
|
||||
EXPECT_FALSE(dom_tree.Dominates(static_cast<ir::BasicBlock*>(nullptr),
|
||||
static_cast<ir::BasicBlock*>(nullptr)));
|
||||
EXPECT_FALSE(dom_tree.Dominates(10, 1));
|
||||
EXPECT_FALSE(dom_tree.Dominates(1, 10));
|
||||
EXPECT_FALSE(dom_tree.Dominates(1, 1));
|
||||
|
@ -102,6 +102,10 @@ Options (in lexicographical order):
|
||||
on function scope variables referenced only with load, store,
|
||||
and constant index access chains in entry point call tree
|
||||
functions.
|
||||
--copy-propagate-arrays
|
||||
Does propagation of memory references when an array is a copy of
|
||||
another. It will only propagate an array if the source is never
|
||||
written to, and the only store to the target is the copy.
|
||||
--eliminate-common-uniform
|
||||
Perform load/load elimination for duplicate uniform values.
|
||||
Converts any constant index access chain uniform loads into
|
||||
@ -524,6 +528,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
|
||||
optimizer->RegisterPass(CreateSimplificationPass());
|
||||
} else if (0 == strcmp(cur_arg, "--ssa-rewrite")) {
|
||||
optimizer->RegisterPass(CreateSSARewritePass());
|
||||
} else if (0 == strcmp(cur_arg, "--copy-propagate-arrays")) {
|
||||
optimizer->RegisterPass(CreateCopyPropagateArraysPass());
|
||||
} else if (0 == strcmp(cur_arg, "--loop-unroll")) {
|
||||
optimizer->RegisterPass(CreateLoopUnrollPass(true));
|
||||
} else if (0 == strcmp(cur_arg, "--loop-unroll-partial")) {
|
||||
|
Loading…
Reference in New Issue
Block a user