mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-30 15:00:06 +00:00
5d602abd66
Adds a pass that looks for redundant instruction in a function, and removes them. The algorithm is a hash table based value numbering algorithm that traverses the dominator tree. This pass removes completely redundant instructions, not partially redundant ones.
225 lines
6.8 KiB
C++
225 lines
6.8 KiB
C++
// Copyright (c) 2017 Google Inc.
|
|
//
|
|
// 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 "value_number_table.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "cfg.h"
|
|
|
|
namespace spvtools {
|
|
namespace opt {
|
|
|
|
uint32_t ValueNumberTable::GetValueNumber(
|
|
spvtools::ir::Instruction* inst) const {
|
|
assert(inst->result_id() != 0 &&
|
|
"inst must have a result id to get a value number.");
|
|
|
|
// Check if this instruction already has a value.
|
|
auto result_id_to_val = id_to_value_.find(inst->result_id());
|
|
if (result_id_to_val != id_to_value_.end()) {
|
|
return result_id_to_val->second;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t ValueNumberTable::AssignValueNumber(ir::Instruction* inst) {
|
|
// If it already has a value return that.
|
|
uint32_t value = GetValueNumber(inst);
|
|
if (value != 0) {
|
|
return value;
|
|
}
|
|
|
|
// If the instruction has other side effects, then it must
|
|
// have its own value number.
|
|
// OpSampledImage and OpImage must remain in the same basic block in which
|
|
// they are used, because of this we will assign each one it own value number.
|
|
if (!context()->IsCombinatorInstruction(inst)) {
|
|
value = TakeNextValueNumber();
|
|
id_to_value_[inst->result_id()] = value;
|
|
return value;
|
|
}
|
|
|
|
switch (inst->opcode()) {
|
|
case SpvOpSampledImage:
|
|
case SpvOpImage:
|
|
case SpvOpVariable:
|
|
value = TakeNextValueNumber();
|
|
id_to_value_[inst->result_id()] = value;
|
|
return value;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// If it is a load from memory that can be modified, we have to assume the
|
|
// memory has been modified, so we give it a new value number.
|
|
//
|
|
// Note that this test will also handle volatile loads because they are not
|
|
// read only. However, if this is ever relaxed because we analyze stores, we
|
|
// will have to add a new case for volatile loads.
|
|
if (inst->IsLoad() && !inst->IsReadOnlyLoad()) {
|
|
value = TakeNextValueNumber();
|
|
id_to_value_[inst->result_id()] = value;
|
|
return value;
|
|
}
|
|
|
|
// When we copy an object, the value numbers should be the same.
|
|
if (inst->opcode() == SpvOpCopyObject) {
|
|
value = GetValueNumber(inst->GetSingleWordInOperand(0));
|
|
if (value != 0) {
|
|
id_to_value_[inst->result_id()] = value;
|
|
return value;
|
|
}
|
|
}
|
|
|
|
// Phi nodes are a type of copy. If all of the inputs have the same value
|
|
// number, then we can assign the result of the phi the same value number.
|
|
if (inst->opcode() == SpvOpPhi) {
|
|
value = GetValueNumber(inst->GetSingleWordInOperand(0));
|
|
if (value != 0) {
|
|
for (uint32_t op = 2; op < inst->NumInOperands(); op += 2) {
|
|
if (value != GetValueNumber(inst->GetSingleWordInOperand(op))) {
|
|
value = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (value != 0) {
|
|
id_to_value_[inst->result_id()] = value;
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Replace all of the operands by their value number. The sign bit will be
|
|
// set to distinguish between an id and a value number.
|
|
ir::Instruction value_ins(context(), inst->opcode(), inst->type_id(),
|
|
inst->result_id(), {});
|
|
for (uint32_t o = 0; o < inst->NumInOperands(); ++o) {
|
|
const ir::Operand& op = inst->GetInOperand(o);
|
|
if (spvIsIdType(op.type)) {
|
|
uint32_t id_value = op.words[0];
|
|
auto use_id_to_val = id_to_value_.find(id_value);
|
|
if (use_id_to_val != id_to_value_.end()) {
|
|
id_value = (1 << 31) | use_id_to_val->second;
|
|
}
|
|
value_ins.AddOperand(ir::Operand(op.type, {id_value}));
|
|
} else {
|
|
value_ins.AddOperand(ir::Operand(op.type, op.words));
|
|
}
|
|
}
|
|
|
|
// TODO: Implement a normal form for opcodes that commute like integer
|
|
// addition. This will let us know that a+b is the same value as b+a.
|
|
|
|
// Otherwise, we check if this value has been computed before.
|
|
auto value_iterator = instruction_to_value_.find(value_ins);
|
|
if (value_iterator != instruction_to_value_.end()) {
|
|
value = id_to_value_[value_iterator->first.result_id()];
|
|
id_to_value_[inst->result_id()] = value;
|
|
return value;
|
|
}
|
|
|
|
// If not, assign it a new value number.
|
|
value = TakeNextValueNumber();
|
|
id_to_value_[inst->result_id()] = value;
|
|
instruction_to_value_[value_ins] = value;
|
|
return value;
|
|
}
|
|
|
|
void ValueNumberTable::BuildDominatorTreeValueNumberTable() {
|
|
// First value number the headers.
|
|
for (auto& inst : context()->annotations()) {
|
|
if (inst.result_id() != 0) {
|
|
AssignValueNumber(&inst);
|
|
}
|
|
}
|
|
|
|
for (auto& inst : context()->capabilities()) {
|
|
if (inst.result_id() != 0) {
|
|
AssignValueNumber(&inst);
|
|
}
|
|
}
|
|
|
|
for (auto& inst : context()->types_values()) {
|
|
if (inst.result_id() != 0) {
|
|
AssignValueNumber(&inst);
|
|
}
|
|
}
|
|
|
|
for (auto& inst : context()->module()->ext_inst_imports()) {
|
|
if (inst.result_id() != 0) {
|
|
AssignValueNumber(&inst);
|
|
}
|
|
}
|
|
|
|
for (ir::Function& func : *context()->module()) {
|
|
// For best results we want to traverse the code in reverse post order.
|
|
// This happens naturally because of the forward referencing rules.
|
|
for (ir::BasicBlock& block : func) {
|
|
for (ir::Instruction& inst : block) {
|
|
if (inst.result_id() != 0) {
|
|
AssignValueNumber(&inst);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ComputeSameValue::operator()(const ir::Instruction& lhs,
|
|
const ir::Instruction& rhs) const {
|
|
if (lhs.result_id() == 0 || rhs.result_id() == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (lhs.opcode() != rhs.opcode()) {
|
|
return false;
|
|
}
|
|
|
|
if (lhs.type_id() != rhs.type_id()) {
|
|
return false;
|
|
}
|
|
|
|
if (lhs.NumInOperands() != rhs.NumInOperands()) {
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < lhs.NumInOperands(); ++i) {
|
|
if (lhs.GetInOperand(i) != rhs.GetInOperand(i)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return lhs.context()->get_decoration_mgr()->HaveTheSameDecorations(
|
|
lhs.result_id(), rhs.result_id());
|
|
}
|
|
|
|
std::size_t ValueTableHash::operator()(
|
|
const spvtools::ir::Instruction& inst) const {
|
|
// We hash the opcode and in-operands, not the result, because we want
|
|
// instructions that are the same except for the result to hash to the
|
|
// same value.
|
|
std::u32string h;
|
|
h.push_back(inst.opcode());
|
|
h.push_back(inst.type_id());
|
|
for (uint32_t i = 0; i < inst.NumInOperands(); ++i) {
|
|
const auto& opnd = inst.GetInOperand(i);
|
|
for (uint32_t word : opnd.words) {
|
|
h.push_back(word);
|
|
}
|
|
}
|
|
return std::hash<std::u32string>()(h);
|
|
}
|
|
} // namespace opt
|
|
} // namespace spvtools
|