SPIRV-Tools/source/opt/remove_duplicates_pass.cpp
Steven Perron f393b0e480 Don't merge types of resources
When doing reflection users care about the names of the variable, the
name of the type, and the name of the members.  Remove duplicates breaks
this because it removes the names one of the types when merging.

To fix this we have to keep the different types around for each
resource.  This commit adds code to remove duplicates to look for the
types uses to describe resources, and make sure they do not get merged.

However, allow merging of a type used in a resource with something not
used in a resource.  Was done when the non resource type came second.

This could have a negative effect on compile time, but it was not
expected to be much.

Fixes https://github.com/KhronosGroup/SPIRV-Tools/issues/1372.
2018-06-27 13:57:07 -04:00

263 lines
7.8 KiB
C++

// Copyright (c) 2017 Pierre Moreau
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "remove_duplicates_pass.h"
#include <cstring>
#include <algorithm>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "decoration_manager.h"
#include "ir_context.h"
#include "opcode.h"
#include "reflect.h"
namespace spvtools {
namespace opt {
using ir::Instruction;
using ir::Module;
using ir::Operand;
using opt::analysis::DecorationManager;
using opt::analysis::DefUseManager;
Pass::Status RemoveDuplicatesPass::Process(ir::IRContext* ir_context) {
bool modified = RemoveDuplicateCapabilities(ir_context);
modified |= RemoveDuplicatesExtInstImports(ir_context);
modified |= RemoveDuplicateTypes(ir_context);
modified |= RemoveDuplicateDecorations(ir_context);
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
bool RemoveDuplicatesPass::RemoveDuplicateCapabilities(
ir::IRContext* ir_context) const {
bool modified = false;
if (ir_context->capabilities().empty()) {
return modified;
}
std::unordered_set<uint32_t> capabilities;
for (auto* i = &*ir_context->capability_begin(); i;) {
auto res = capabilities.insert(i->GetSingleWordOperand(0u));
if (res.second) {
// Never seen before, keep it.
i = i->NextNode();
} else {
// It's a duplicate, remove it.
i = ir_context->KillInst(i);
modified = true;
}
}
return modified;
}
bool RemoveDuplicatesPass::RemoveDuplicatesExtInstImports(
ir::IRContext* ir_context) const {
bool modified = false;
if (ir_context->ext_inst_imports().empty()) {
return modified;
}
std::unordered_map<std::string, SpvId> ext_inst_imports;
for (auto* i = &*ir_context->ext_inst_import_begin(); i;) {
auto res = ext_inst_imports.emplace(
reinterpret_cast<const char*>(i->GetInOperand(0u).words.data()),
i->result_id());
if (res.second) {
// Never seen before, keep it.
i = i->NextNode();
} else {
// It's a duplicate, remove it.
ir_context->ReplaceAllUsesWith(i->result_id(), res.first->second);
i = ir_context->KillInst(i);
modified = true;
}
}
return modified;
}
bool RemoveDuplicatesPass::RemoveDuplicateTypes(
ir::IRContext* ir_context) const {
bool modified = false;
if (ir_context->types_values().empty()) {
return modified;
}
std::unordered_set<uint32_t> types_ids_of_resources;
for (auto& decoration_inst : ir_context->annotations()) {
if (decoration_inst.opcode() != SpvOpDecorate) {
continue;
}
if (decoration_inst.GetSingleWordInOperand(1) != SpvDecorationBinding) {
continue;
}
uint32_t var_id = decoration_inst.GetSingleWordInOperand(0);
ir::Instruction* var_inst = ir_context->get_def_use_mgr()->GetDef(var_id);
uint32_t type_id = var_inst->type_id();
AddStructuresToSet(type_id, ir_context, &types_ids_of_resources);
}
std::vector<Instruction*> visited_types;
std::vector<Instruction*> to_delete;
for (auto* i = &*ir_context->types_values_begin(); i; i = i->NextNode()) {
// We only care about types.
if (!spvOpcodeGeneratesType((i->opcode())) &&
i->opcode() != SpvOpTypeForwardPointer) {
continue;
}
// Is the current type equal to one of the types we have aready visited?
SpvId id_to_keep = 0u;
bool i_is_resource_type = types_ids_of_resources.count(i->result_id()) != 0;
// TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
// ResultIdTrie from unify_const_pass.cpp for this.
for (auto& j : visited_types) {
if (!j) {
continue;
}
if (AreTypesEqual(*i, *j, ir_context)) {
if (!i_is_resource_type) {
id_to_keep = j->result_id();
break;
} else if (!types_ids_of_resources.count(j->result_id())) {
ir_context->KillNamesAndDecorates(j->result_id());
ir_context->ReplaceAllUsesWith(j->result_id(), i->result_id());
modified = true;
to_delete.emplace_back(j);
j = nullptr;
}
}
}
if (id_to_keep == 0u) {
// This is a never seen before type, keep it around.
visited_types.emplace_back(i);
} else {
// The same type has already been seen before, remove this one.
ir_context->KillNamesAndDecorates(i->result_id());
ir_context->ReplaceAllUsesWith(i->result_id(), id_to_keep);
modified = true;
to_delete.emplace_back(i);
}
}
for (auto i : to_delete) {
ir_context->KillInst(i);
}
return modified;
} // namespace opt
// TODO(pierremoreau): Duplicate decoration groups should be removed. For
// example, in
// OpDecorate %1 Constant
// %1 = OpDecorationGroup
// OpDecorate %2 Constant
// %2 = OpDecorationGroup
// OpGroupDecorate %1 %3
// OpGroupDecorate %2 %4
// group %2 could be removed.
bool RemoveDuplicatesPass::RemoveDuplicateDecorations(
ir::IRContext* ir_context) const {
bool modified = false;
std::vector<const Instruction*> visited_decorations;
opt::analysis::DecorationManager decoration_manager(ir_context->module());
for (auto* i = &*ir_context->annotation_begin(); i;) {
// Is the current decoration equal to one of the decorations we have aready
// visited?
bool already_visited = false;
// TODO(dneto0): Use a trie to avoid quadratic behaviour? Extract the
// ResultIdTrie from unify_const_pass.cpp for this.
for (const Instruction* j : visited_decorations) {
if (decoration_manager.AreDecorationsTheSame(&*i, j, false)) {
already_visited = true;
break;
}
}
if (!already_visited) {
// This is a never seen before decoration, keep it around.
visited_decorations.emplace_back(&*i);
i = i->NextNode();
} else {
// The same decoration has already been seen before, remove this one.
modified = true;
i = ir_context->KillInst(i);
}
}
return modified;
}
bool RemoveDuplicatesPass::AreTypesEqual(const Instruction& inst1,
const Instruction& inst2,
ir::IRContext* context) {
if (inst1.opcode() != inst2.opcode()) return false;
if (!ir::IsTypeInst(inst1.opcode())) return false;
const analysis::Type* type1 =
context->get_type_mgr()->GetType(inst1.result_id());
const analysis::Type* type2 =
context->get_type_mgr()->GetType(inst2.result_id());
if (type1 && type2 && *type1 == *type2) return true;
return false;
}
void RemoveDuplicatesPass::AddStructuresToSet(
uint32_t type_id, ir::IRContext* ctx,
std::unordered_set<uint32_t>* set_of_ids) const {
ir::Instruction* type_inst = ctx->get_def_use_mgr()->GetDef(type_id);
switch (type_inst->opcode()) {
case SpvOpTypeStruct:
set_of_ids->insert(type_id);
for (uint32_t i = 0; i < type_inst->NumInOperands(); ++i) {
AddStructuresToSet(type_inst->GetSingleWordInOperand(i), ctx,
set_of_ids);
}
break;
case SpvOpTypeArray:
case SpvOpTypeRuntimeArray:
set_of_ids->insert(type_id);
AddStructuresToSet(type_inst->GetSingleWordInOperand(0), ctx, set_of_ids);
break;
case SpvOpTypePointer:
set_of_ids->insert(type_id);
AddStructuresToSet(type_inst->GetSingleWordInOperand(1), ctx, set_of_ids);
break;
default:
break;
}
}
} // namespace opt
} // namespace spvtools