mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-22 11:40:05 +00:00
spirv-reduce: Remove unused struct members (#3329)
Adds a reduction pass to remove unused members from structs.
This commit is contained in:
parent
a6b0e132ec
commit
3118276370
@ -31,7 +31,9 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
|
||||
remove_instruction_reduction_opportunity.h
|
||||
remove_selection_reduction_opportunity.h
|
||||
remove_selection_reduction_opportunity_finder.h
|
||||
remove_struct_member_reduction_opportunity.h
|
||||
remove_unused_instruction_reduction_opportunity_finder.h
|
||||
remove_unused_struct_member_reduction_opportunity_finder.h
|
||||
structured_loop_to_selection_reduction_opportunity.h
|
||||
structured_loop_to_selection_reduction_opportunity_finder.h
|
||||
conditional_branch_to_simple_conditional_branch_opportunity_finder.h
|
||||
@ -57,7 +59,9 @@ set(SPIRV_TOOLS_REDUCE_SOURCES
|
||||
remove_instruction_reduction_opportunity.cpp
|
||||
remove_selection_reduction_opportunity.cpp
|
||||
remove_selection_reduction_opportunity_finder.cpp
|
||||
remove_struct_member_reduction_opportunity.cpp
|
||||
remove_unused_instruction_reduction_opportunity_finder.cpp
|
||||
remove_unused_struct_member_reduction_opportunity_finder.cpp
|
||||
structured_loop_to_selection_reduction_opportunity.cpp
|
||||
structured_loop_to_selection_reduction_opportunity_finder.cpp
|
||||
conditional_branch_to_simple_conditional_branch_opportunity_finder.cpp
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "source/reduce/remove_function_reduction_opportunity_finder.h"
|
||||
#include "source/reduce/remove_selection_reduction_opportunity_finder.h"
|
||||
#include "source/reduce/remove_unused_instruction_reduction_opportunity_finder.h"
|
||||
#include "source/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
|
||||
#include "source/reduce/simple_conditional_branch_to_branch_opportunity_finder.h"
|
||||
#include "source/reduce/structured_loop_to_selection_reduction_opportunity_finder.h"
|
||||
#include "source/spirv_reducer_options.h"
|
||||
@ -126,6 +127,8 @@ void Reducer::AddDefaultReductionPasses() {
|
||||
ConditionalBranchToSimpleConditionalBranchOpportunityFinder>());
|
||||
AddReductionPass(
|
||||
spvtools::MakeUnique<SimpleConditionalBranchToBranchOpportunityFinder>());
|
||||
AddReductionPass(spvtools::MakeUnique<
|
||||
RemoveUnusedStructMemberReductionOpportunityFinder>());
|
||||
|
||||
// Cleanup passes.
|
||||
|
||||
|
208
source/reduce/remove_struct_member_reduction_opportunity.cpp
Normal file
208
source/reduce/remove_struct_member_reduction_opportunity.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
// Copyright (c) 2020 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/reduce/remove_struct_member_reduction_opportunity.h"
|
||||
|
||||
#include "source/opt/ir_context.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
bool RemoveStructMemberReductionOpportunity::PreconditionHolds() {
|
||||
return struct_type_->NumInOperands() == original_number_of_members_;
|
||||
}
|
||||
|
||||
void RemoveStructMemberReductionOpportunity::Apply() {
|
||||
std::set<opt::Instruction*> decorations_to_kill;
|
||||
|
||||
// We need to remove decorations that target the removed struct member, and
|
||||
// adapt decorations that target later struct members by decrementing the
|
||||
// member identifier. We also need to adapt composite construction
|
||||
// instructions so that no id is provided for the member being removed.
|
||||
//
|
||||
// To do this, we consider every use of the struct type.
|
||||
struct_type_->context()->get_def_use_mgr()->ForEachUse(
|
||||
struct_type_, [this, &decorations_to_kill](opt::Instruction* user,
|
||||
uint32_t /*operand_index*/) {
|
||||
switch (user->opcode()) {
|
||||
case SpvOpCompositeConstruct:
|
||||
case SpvOpConstantComposite:
|
||||
// This use is constructing a composite of the struct type, so we
|
||||
// must remove the id that was provided for the member we are
|
||||
// removing.
|
||||
user->RemoveInOperand(member_index_);
|
||||
break;
|
||||
case SpvOpMemberDecorate:
|
||||
// This use is decorating a member of the struct.
|
||||
if (user->GetSingleWordInOperand(1) == member_index_) {
|
||||
// The member we are removing is being decorated, so we record
|
||||
// that we need to get rid of the decoration.
|
||||
decorations_to_kill.insert(user);
|
||||
} else if (user->GetSingleWordInOperand(1) > member_index_) {
|
||||
// A member beyond the one we are removing is being decorated, so
|
||||
// we adjust the index that identifies the member.
|
||||
user->SetInOperand(1, {user->GetSingleWordInOperand(1) - 1});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Get rid of all the decorations that were found to target the member being
|
||||
// removed.
|
||||
for (auto decoration_to_kill : decorations_to_kill) {
|
||||
decoration_to_kill->context()->KillInst(decoration_to_kill);
|
||||
}
|
||||
|
||||
// We now look through all instructions that access composites via sequences
|
||||
// of indices. Every time we find an index into the struct whose member is
|
||||
// being removed, and if the member being accessed comes after the member
|
||||
// being removed, we need to adjust the index accordingly.
|
||||
//
|
||||
// We go through every relevant instruction in every block of every function,
|
||||
// and invoke a helper to adjust it.
|
||||
auto context = struct_type_->context();
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto& inst : block) {
|
||||
switch (inst.opcode()) {
|
||||
case SpvOpAccessChain:
|
||||
case SpvOpInBoundsAccessChain: {
|
||||
// These access chain instructions take sequences of ids for
|
||||
// indexing, starting from input operand 1.
|
||||
auto composite_type_id =
|
||||
context->get_def_use_mgr()
|
||||
->GetDef(context->get_def_use_mgr()
|
||||
->GetDef(inst.GetSingleWordInOperand(0))
|
||||
->type_id())
|
||||
->GetSingleWordInOperand(1);
|
||||
AdjustAccessedIndices(composite_type_id, 1, false, context, &inst);
|
||||
} break;
|
||||
case SpvOpPtrAccessChain:
|
||||
case SpvOpInBoundsPtrAccessChain: {
|
||||
// These access chain instructions take sequences of ids for
|
||||
// indexing, starting from input operand 2.
|
||||
auto composite_type_id =
|
||||
context->get_def_use_mgr()
|
||||
->GetDef(context->get_def_use_mgr()
|
||||
->GetDef(inst.GetSingleWordInOperand(1))
|
||||
->type_id())
|
||||
->GetSingleWordInOperand(1);
|
||||
AdjustAccessedIndices(composite_type_id, 2, false, context, &inst);
|
||||
} break;
|
||||
case SpvOpCompositeExtract: {
|
||||
// OpCompositeExtract uses literals for indexing, starting at input
|
||||
// operand 1.
|
||||
auto composite_type_id =
|
||||
context->get_def_use_mgr()
|
||||
->GetDef(inst.GetSingleWordInOperand(0))
|
||||
->type_id();
|
||||
AdjustAccessedIndices(composite_type_id, 1, true, context, &inst);
|
||||
} break;
|
||||
case SpvOpCompositeInsert: {
|
||||
// OpCompositeInsert uses literals for indexing, starting at input
|
||||
// operand 2.
|
||||
auto composite_type_id =
|
||||
context->get_def_use_mgr()
|
||||
->GetDef(inst.GetSingleWordInOperand(1))
|
||||
->type_id();
|
||||
AdjustAccessedIndices(composite_type_id, 2, true, context, &inst);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the member from the struct type.
|
||||
struct_type_->RemoveInOperand(member_index_);
|
||||
}
|
||||
|
||||
void RemoveStructMemberReductionOpportunity::AdjustAccessedIndices(
|
||||
uint32_t composite_type_id, uint32_t first_index_input_operand,
|
||||
bool literal_indices, opt::IRContext* context,
|
||||
opt::Instruction* composite_access_instruction) const {
|
||||
// Walk the series of types that are encountered by following the
|
||||
// instruction's sequence of indices. For all types except structs, this is
|
||||
// routine: the type of the composite dictates what the next type will be
|
||||
// regardless of the specific index value.
|
||||
uint32_t next_type = composite_type_id;
|
||||
for (uint32_t i = first_index_input_operand;
|
||||
i < composite_access_instruction->NumInOperands(); i++) {
|
||||
auto type_inst = context->get_def_use_mgr()->GetDef(next_type);
|
||||
switch (type_inst->opcode()) {
|
||||
case SpvOpTypeArray:
|
||||
case SpvOpTypeMatrix:
|
||||
case SpvOpTypeRuntimeArray:
|
||||
case SpvOpTypeVector:
|
||||
next_type = type_inst->GetSingleWordInOperand(0);
|
||||
break;
|
||||
case SpvOpTypeStruct: {
|
||||
// Struct types are special becuase (a) we may need to adjust the index
|
||||
// being used, if the struct type is the one from which we are removing
|
||||
// a member, and (b) the type encountered by following the current index
|
||||
// is dependent on the value of the index.
|
||||
|
||||
// Work out the member being accessed. If literal indexing is used this
|
||||
// is simple; otherwise we need to look up the id of the constant
|
||||
// instruction being used as an index and get the value of the constant.
|
||||
uint32_t index_operand =
|
||||
composite_access_instruction->GetSingleWordInOperand(i);
|
||||
uint32_t member = literal_indices ? index_operand
|
||||
: context->get_def_use_mgr()
|
||||
->GetDef(index_operand)
|
||||
->GetSingleWordInOperand(0);
|
||||
|
||||
// The next type we will consider is obtained by looking up the struct
|
||||
// type at |member|.
|
||||
next_type = type_inst->GetSingleWordInOperand(member);
|
||||
|
||||
if (type_inst == struct_type_ && member > member_index_) {
|
||||
// The struct type is the struct from which we are removing a member,
|
||||
// and the member being accessed is beyond the member we are removing.
|
||||
// We thus need to decrement the index by 1.
|
||||
uint32_t new_in_operand;
|
||||
if (literal_indices) {
|
||||
// With literal indexing this is straightforward.
|
||||
new_in_operand = member - 1;
|
||||
} else {
|
||||
// With id-based indexing this is more tricky: we need to find or
|
||||
// create a constant instruction whose value is one less than
|
||||
// |member|, and use the id of this constant as the replacement
|
||||
// input operand.
|
||||
auto constant_inst =
|
||||
context->get_def_use_mgr()->GetDef(index_operand);
|
||||
auto int_type = context->get_type_mgr()
|
||||
->GetType(constant_inst->type_id())
|
||||
->AsInteger();
|
||||
auto new_index_constant =
|
||||
opt::analysis::IntConstant(int_type, {member - 1});
|
||||
new_in_operand = context->get_constant_mgr()
|
||||
->GetDefiningInstruction(&new_index_constant)
|
||||
->result_id();
|
||||
}
|
||||
composite_access_instruction->SetInOperand(i, {new_in_operand});
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
assert(0 && "Unknown composite type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reduce
|
||||
} // namespace spvtools
|
84
source/reduce/remove_struct_member_reduction_opportunity.h
Normal file
84
source/reduce/remove_struct_member_reduction_opportunity.h
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2020 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_
|
||||
#define SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_
|
||||
|
||||
#include "source/reduce/reduction_opportunity.h"
|
||||
|
||||
#include "source/opt/instruction.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
// An opportunity for removing a member from a struct type, adjusting all uses
|
||||
// of the struct accordingly.
|
||||
class RemoveStructMemberReductionOpportunity : public ReductionOpportunity {
|
||||
public:
|
||||
// Constructs a reduction opportunity from the struct type |struct_type|, for
|
||||
// removal of member |member_index|.
|
||||
RemoveStructMemberReductionOpportunity(opt::Instruction* struct_type,
|
||||
uint32_t member_index)
|
||||
: struct_type_(struct_type),
|
||||
member_index_(member_index),
|
||||
original_number_of_members_(struct_type->NumInOperands()) {}
|
||||
|
||||
// Opportunities to remove fields from a common struct type mutually
|
||||
// invalidate each other. We guard against this by requiring that the struct
|
||||
// still has the number of members it had when the opportunity was created.
|
||||
bool PreconditionHolds() override;
|
||||
|
||||
protected:
|
||||
void Apply() override;
|
||||
|
||||
private:
|
||||
// |composite_access_instruction| is an instruction that accesses a composite
|
||||
// id using either a series of literal indices (e.g. in the case of
|
||||
// OpCompositeInsert) or a series of index ids (e.g. in the case of
|
||||
// OpAccessChain).
|
||||
//
|
||||
// This function adjusts the indices that are used by
|
||||
// |composite_access_instruction| to that whenever an index is accessing a
|
||||
// member of |struct_type_|, it is decremented if the member is beyond
|
||||
// |member_index_|, to account for the removal of the |member_index_|-th
|
||||
// member.
|
||||
//
|
||||
// |composite_type_id| is the id of the composite type that the series of
|
||||
// indices is to be applied to.
|
||||
//
|
||||
// |first_index_input_operand| specifies the first input operand that is an
|
||||
// index.
|
||||
//
|
||||
// |literal_indices| specifies whether indices are given as literals (true),
|
||||
// or as ids (false).
|
||||
//
|
||||
// If id-based indexing is used, this function will add a constant for
|
||||
// |member_index_| - 1 to the module if needed.
|
||||
void AdjustAccessedIndices(
|
||||
uint32_t composite_type_id, uint32_t first_index_input_operand,
|
||||
bool literal_indices, opt::IRContext* context,
|
||||
opt::Instruction* composite_access_instruction) const;
|
||||
|
||||
// The struct type from which a member is to be removed.
|
||||
opt::Instruction* struct_type_;
|
||||
|
||||
uint32_t member_index_;
|
||||
|
||||
uint32_t original_number_of_members_;
|
||||
};
|
||||
|
||||
} // namespace reduce
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_REDUCE_REMOVE_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_H_
|
@ -0,0 +1,193 @@
|
||||
// Copyright (c) 2020 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/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "source/reduce/remove_struct_member_reduction_opportunity.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>>
|
||||
RemoveUnusedStructMemberReductionOpportunityFinder::GetAvailableOpportunities(
|
||||
opt::IRContext* context) const {
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> result;
|
||||
|
||||
// We track those struct members that are never accessed. We do this by
|
||||
// associating a member index to all the structs that have this member index
|
||||
// but do not use it. This representation is designed to allow reduction
|
||||
// opportunities to be provided in a useful manner, so that opportunities
|
||||
// associated with the same struct are unlikely to be adjacent.
|
||||
std::map<uint32_t, std::set<opt::Instruction*>> unused_member_to_structs;
|
||||
|
||||
// Consider every struct type in the module.
|
||||
for (auto& type_or_value : context->types_values()) {
|
||||
if (type_or_value.opcode() != SpvOpTypeStruct) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Initially, we assume that *every* member of the struct is unused. We
|
||||
// then refine this based on observed uses.
|
||||
std::set<uint32_t> unused_members;
|
||||
for (uint32_t i = 0; i < type_or_value.NumInOperands(); i++) {
|
||||
unused_members.insert(i);
|
||||
}
|
||||
|
||||
// A separate reduction pass deals with removal of names. If a struct
|
||||
// member is still named, we treat it as being used.
|
||||
context->get_def_use_mgr()->ForEachUse(
|
||||
&type_or_value,
|
||||
[&unused_members](opt::Instruction* user, uint32_t /*operand_index*/) {
|
||||
switch (user->opcode()) {
|
||||
case SpvOpMemberName:
|
||||
unused_members.erase(user->GetSingleWordInOperand(1));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
for (uint32_t member : unused_members) {
|
||||
if (!unused_member_to_structs.count(member)) {
|
||||
unused_member_to_structs.insert(
|
||||
{member, std::set<opt::Instruction*>()});
|
||||
}
|
||||
unused_member_to_structs.at(member).insert(&type_or_value);
|
||||
}
|
||||
}
|
||||
|
||||
// We now go through every instruction that might index into a struct, and
|
||||
// refine our tracking of which struct members are used based on the struct
|
||||
// indexing we observe. We cannot just go through all uses of a struct type
|
||||
// because the type is not necessarily even referenced, e.g. when walking
|
||||
// arrays of structs.
|
||||
for (auto& function : *context->module()) {
|
||||
for (auto& block : function) {
|
||||
for (auto& inst : block) {
|
||||
switch (inst.opcode()) {
|
||||
// For each indexing operation we observe, we invoke a helper to
|
||||
// remove from our map those struct indices that are found to be used.
|
||||
// The way the helper is invoked depends on whether the instruction
|
||||
// uses literal or id indices, and the offset into the instruction's
|
||||
// input operands from which index operands are provided.
|
||||
case SpvOpAccessChain:
|
||||
case SpvOpInBoundsAccessChain: {
|
||||
auto composite_type_id =
|
||||
context->get_def_use_mgr()
|
||||
->GetDef(context->get_def_use_mgr()
|
||||
->GetDef(inst.GetSingleWordInOperand(0))
|
||||
->type_id())
|
||||
->GetSingleWordInOperand(1);
|
||||
MarkAccessedMembersAsUsed(context, composite_type_id, 1, false,
|
||||
inst, &unused_member_to_structs);
|
||||
} break;
|
||||
case SpvOpPtrAccessChain:
|
||||
case SpvOpInBoundsPtrAccessChain: {
|
||||
auto composite_type_id =
|
||||
context->get_def_use_mgr()
|
||||
->GetDef(context->get_def_use_mgr()
|
||||
->GetDef(inst.GetSingleWordInOperand(1))
|
||||
->type_id())
|
||||
->GetSingleWordInOperand(1);
|
||||
MarkAccessedMembersAsUsed(context, composite_type_id, 2, false,
|
||||
inst, &unused_member_to_structs);
|
||||
} break;
|
||||
case SpvOpCompositeExtract: {
|
||||
auto composite_type_id =
|
||||
context->get_def_use_mgr()
|
||||
->GetDef(inst.GetSingleWordInOperand(0))
|
||||
->type_id();
|
||||
MarkAccessedMembersAsUsed(context, composite_type_id, 1, true, inst,
|
||||
&unused_member_to_structs);
|
||||
} break;
|
||||
case SpvOpCompositeInsert: {
|
||||
auto composite_type_id =
|
||||
context->get_def_use_mgr()
|
||||
->GetDef(inst.GetSingleWordInOperand(1))
|
||||
->type_id();
|
||||
MarkAccessedMembersAsUsed(context, composite_type_id, 2, true, inst,
|
||||
&unused_member_to_structs);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We now know those struct indices that are unsed, and we make a reduction
|
||||
// opportunity for each of them. By mapping each relevant member index to the
|
||||
// structs in which it is unsed, we will group all opportunities to remove
|
||||
// member k of a struct (for some k) together. This reduces the likelihood
|
||||
// that opportunities to remove members from the same struct will be adjacent,
|
||||
// which is good because such opportunities mutually disable one another.
|
||||
for (auto& entry : unused_member_to_structs) {
|
||||
for (auto struct_type : entry.second) {
|
||||
result.push_back(MakeUnique<RemoveStructMemberReductionOpportunity>(
|
||||
struct_type, entry.first));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void RemoveUnusedStructMemberReductionOpportunityFinder::
|
||||
MarkAccessedMembersAsUsed(
|
||||
opt::IRContext* context, uint32_t composite_type_id,
|
||||
uint32_t first_index_in_operand, bool literal_indices,
|
||||
const opt::Instruction& composite_access_instruction,
|
||||
std::map<uint32_t, std::set<opt::Instruction*>>*
|
||||
unused_member_to_structs) const {
|
||||
uint32_t next_type = composite_type_id;
|
||||
for (uint32_t i = first_index_in_operand;
|
||||
i < composite_access_instruction.NumInOperands(); i++) {
|
||||
auto type_inst = context->get_def_use_mgr()->GetDef(next_type);
|
||||
switch (type_inst->opcode()) {
|
||||
case SpvOpTypeArray:
|
||||
case SpvOpTypeMatrix:
|
||||
case SpvOpTypeRuntimeArray:
|
||||
case SpvOpTypeVector:
|
||||
next_type = type_inst->GetSingleWordInOperand(0);
|
||||
break;
|
||||
case SpvOpTypeStruct: {
|
||||
uint32_t index_operand =
|
||||
composite_access_instruction.GetSingleWordInOperand(i);
|
||||
uint32_t member = literal_indices ? index_operand
|
||||
: context->get_def_use_mgr()
|
||||
->GetDef(index_operand)
|
||||
->GetSingleWordInOperand(0);
|
||||
// Remove the struct type from the struct types associated with this
|
||||
// member index, but only if a set of struct types is known to be
|
||||
// associated with this member index.
|
||||
if (unused_member_to_structs->count(member)) {
|
||||
unused_member_to_structs->at(member).erase(type_inst);
|
||||
}
|
||||
next_type = type_inst->GetSingleWordInOperand(member);
|
||||
} break;
|
||||
default:
|
||||
assert(0 && "Unknown composite type.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string RemoveUnusedStructMemberReductionOpportunityFinder::GetName()
|
||||
const {
|
||||
return "RemoveUnusedStructMemberReductionOpportunityFinder";
|
||||
}
|
||||
|
||||
} // namespace reduce
|
||||
} // namespace spvtools
|
@ -0,0 +1,61 @@
|
||||
// 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 SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_
|
||||
#define SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_
|
||||
|
||||
#include "source/reduce/reduction_opportunity_finder.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
|
||||
// A finder for opportunities to remove struct members that are not explicitly
|
||||
// used by extract, insert or access chain instructions.
|
||||
class RemoveUnusedStructMemberReductionOpportunityFinder
|
||||
: public ReductionOpportunityFinder {
|
||||
public:
|
||||
RemoveUnusedStructMemberReductionOpportunityFinder() = default;
|
||||
|
||||
~RemoveUnusedStructMemberReductionOpportunityFinder() override = default;
|
||||
|
||||
std::string GetName() const final;
|
||||
|
||||
std::vector<std::unique_ptr<ReductionOpportunity>> GetAvailableOpportunities(
|
||||
opt::IRContext* context) const final;
|
||||
|
||||
private:
|
||||
// A helper method to update |unused_members_to_structs| by removing from it
|
||||
// all struct member accesses that take place in
|
||||
// |composite_access_instruction|.
|
||||
//
|
||||
// |composite_type_id| is the type of the root object indexed into by the
|
||||
// instruction.
|
||||
//
|
||||
// |first_index_in_operand| provides indicates where in the input operands the
|
||||
// sequence of indices begins.
|
||||
//
|
||||
// |literal_indices| indicates whether indices are literals (true) or ids
|
||||
// (false).
|
||||
void MarkAccessedMembersAsUsed(
|
||||
opt::IRContext* context, uint32_t composite_type_id,
|
||||
uint32_t first_index_in_operand, bool literal_indices,
|
||||
const opt::Instruction& composite_access_instruction,
|
||||
std::map<uint32_t, std::set<opt::Instruction*>>* unused_member_to_structs)
|
||||
const;
|
||||
};
|
||||
|
||||
} // namespace reduce
|
||||
} // namespace spvtools
|
||||
|
||||
#endif // SOURCE_REDUCE_REMOVE_UNUSED_STRUCT_MEMBER_REDUCTION_OPPORTUNITY_FINDER_H_
|
@ -25,6 +25,7 @@ add_spvtools_unittest(TARGET reduce
|
||||
remove_function_test.cpp
|
||||
remove_selection_test.cpp
|
||||
remove_unused_instruction_test.cpp
|
||||
remove_unused_struct_member_test.cpp
|
||||
structured_loop_to_selection_test.cpp
|
||||
validation_during_reduction_test.cpp
|
||||
conditional_branch_to_simple_conditional_branch_test.cpp
|
||||
|
238
test/reduce/remove_unused_struct_member_test.cpp
Normal file
238
test/reduce/remove_unused_struct_member_test.cpp
Normal file
@ -0,0 +1,238 @@
|
||||
// Copyright (c) 2020 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/reduce/remove_unused_struct_member_reduction_opportunity_finder.h"
|
||||
|
||||
#include "source/opt/build_module.h"
|
||||
#include "source/reduce/reduction_opportunity.h"
|
||||
#include "test/reduce/reduce_test_util.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace reduce {
|
||||
namespace {
|
||||
|
||||
TEST(RemoveUnusedStructMemberTest, RemoveOneMember) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeInt 32 1
|
||||
%7 = OpTypeStruct %6 %6
|
||||
%8 = OpTypePointer Function %7
|
||||
%50 = OpConstant %6 0
|
||||
%10 = OpConstant %6 1
|
||||
%11 = OpConstant %6 2
|
||||
%12 = OpConstantComposite %7 %10 %11
|
||||
%13 = OpConstant %6 4
|
||||
%14 = OpTypePointer Function %6
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%9 = OpVariable %8 Function
|
||||
OpStore %9 %12
|
||||
%15 = OpAccessChain %14 %9 %10
|
||||
%22 = OpInBoundsAccessChain %14 %9 %10
|
||||
%20 = OpLoad %7 %9
|
||||
%21 = OpCompositeExtract %6 %20 1
|
||||
%23 = OpCompositeInsert %7 %10 %20 1
|
||||
OpStore %15 %13
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto context =
|
||||
BuildModule(env, consumer, shader, kReduceAssembleOption);
|
||||
|
||||
auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
|
||||
.GetAvailableOpportunities(context.get());
|
||||
ASSERT_EQ(1, ops.size());
|
||||
ASSERT_TRUE(ops[0]->PreconditionHolds());
|
||||
ops[0]->TryToApply();
|
||||
|
||||
CheckValid(env, context.get());
|
||||
|
||||
std::string expected = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeInt 32 1
|
||||
%7 = OpTypeStruct %6
|
||||
%8 = OpTypePointer Function %7
|
||||
%50 = OpConstant %6 0
|
||||
%10 = OpConstant %6 1
|
||||
%11 = OpConstant %6 2
|
||||
%12 = OpConstantComposite %7 %11
|
||||
%13 = OpConstant %6 4
|
||||
%14 = OpTypePointer Function %6
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%9 = OpVariable %8 Function
|
||||
OpStore %9 %12
|
||||
%15 = OpAccessChain %14 %9 %50
|
||||
%22 = OpInBoundsAccessChain %14 %9 %50
|
||||
%20 = OpLoad %7 %9
|
||||
%21 = OpCompositeExtract %6 %20 0
|
||||
%23 = OpCompositeInsert %7 %10 %20 0
|
||||
OpStore %15 %13
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CheckEqual(env, expected, context.get());
|
||||
}
|
||||
|
||||
TEST(RemoveUnusedStructMemberTest, RemoveUniformBufferMember) {
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpMemberDecorate %10 0 Offset 0
|
||||
OpMemberDecorate %10 1 Offset 4
|
||||
OpDecorate %10 Block
|
||||
OpDecorate %12 DescriptorSet 0
|
||||
OpDecorate %12 Binding 0
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypePointer Function %6
|
||||
%9 = OpTypeInt 32 1
|
||||
%10 = OpTypeStruct %9 %6
|
||||
%11 = OpTypePointer Uniform %10
|
||||
%12 = OpVariable %11 Uniform
|
||||
%13 = OpConstant %9 1
|
||||
%20 = OpConstant %9 0
|
||||
%14 = OpTypePointer Uniform %6
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%8 = OpVariable %7 Function
|
||||
%15 = OpAccessChain %14 %12 %13
|
||||
%16 = OpLoad %6 %15
|
||||
OpStore %8 %16
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto context =
|
||||
BuildModule(env, consumer, shader, kReduceAssembleOption);
|
||||
|
||||
auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
|
||||
.GetAvailableOpportunities(context.get());
|
||||
ASSERT_EQ(1, ops.size());
|
||||
ASSERT_TRUE(ops[0]->PreconditionHolds());
|
||||
ops[0]->TryToApply();
|
||||
|
||||
CheckValid(env, context.get());
|
||||
|
||||
std::string expected = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpMemberDecorate %10 0 Offset 4
|
||||
OpDecorate %10 Block
|
||||
OpDecorate %12 DescriptorSet 0
|
||||
OpDecorate %12 Binding 0
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeFloat 32
|
||||
%7 = OpTypePointer Function %6
|
||||
%9 = OpTypeInt 32 1
|
||||
%10 = OpTypeStruct %6
|
||||
%11 = OpTypePointer Uniform %10
|
||||
%12 = OpVariable %11 Uniform
|
||||
%13 = OpConstant %9 1
|
||||
%20 = OpConstant %9 0
|
||||
%14 = OpTypePointer Uniform %6
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%8 = OpVariable %7 Function
|
||||
%15 = OpAccessChain %14 %12 %20
|
||||
%16 = OpLoad %6 %15
|
||||
OpStore %8 %16
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
CheckEqual(env, expected, context.get());
|
||||
}
|
||||
|
||||
TEST(RemoveUnusedStructMemberTest, DoNotRemoveNamedMemberRemoveOneMember) {
|
||||
// This illustrates that naming a member is enough to prevent its removal.
|
||||
// Removal of names is done by a different pass.
|
||||
|
||||
std::string shader = R"(
|
||||
OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %4 "main"
|
||||
OpExecutionMode %4 OriginUpperLeft
|
||||
OpSource ESSL 310
|
||||
OpMemberName %7 0 "someName"
|
||||
OpMemberName %7 1 "someOtherName"
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeFunction %2
|
||||
%6 = OpTypeInt 32 1
|
||||
%7 = OpTypeStruct %6 %6
|
||||
%8 = OpTypePointer Function %7
|
||||
%50 = OpConstant %6 0
|
||||
%10 = OpConstant %6 1
|
||||
%11 = OpConstant %6 2
|
||||
%12 = OpConstantComposite %7 %10 %11
|
||||
%13 = OpConstant %6 4
|
||||
%14 = OpTypePointer Function %6
|
||||
%4 = OpFunction %2 None %3
|
||||
%5 = OpLabel
|
||||
%9 = OpVariable %8 Function
|
||||
OpStore %9 %12
|
||||
%15 = OpAccessChain %14 %9 %10
|
||||
%22 = OpInBoundsAccessChain %14 %9 %10
|
||||
%20 = OpLoad %7 %9
|
||||
%21 = OpCompositeExtract %6 %20 1
|
||||
%23 = OpCompositeInsert %7 %10 %20 1
|
||||
OpStore %15 %13
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const auto env = SPV_ENV_UNIVERSAL_1_3;
|
||||
const auto consumer = nullptr;
|
||||
const auto context =
|
||||
BuildModule(env, consumer, shader, kReduceAssembleOption);
|
||||
|
||||
auto ops = RemoveUnusedStructMemberReductionOpportunityFinder()
|
||||
.GetAvailableOpportunities(context.get());
|
||||
ASSERT_EQ(0, ops.size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace reduce
|
||||
} // namespace spvtools
|
Loading…
Reference in New Issue
Block a user