Add FlattenDecoration transform

Add --flatten-decorations to spirv-opt

Flattens decoration groups.  That is, replace OpDecorationGroup
and its uses in OpGroupDecorate and OpGroupMemberDecorate with
ordinary OpDecorate and OpMemberDecorate instructions.

Fixes https://github.com/KhronosGroup/SPIRV-Tools/issues/602
This commit is contained in:
David Neto 2017-04-01 16:10:16 -04:00
parent 5c3c054c1f
commit 11a867f412
10 changed files with 460 additions and 0 deletions

View File

@ -2,6 +2,7 @@ Revision history for SPIRV-Tools
v2016.7-dev 2017-01-06
- Optimizer: Add inlining of all function calls in entry points
- Optimizer: Add flattening of decoration groups. Fixes #602
- Add build target spirv-tools-vimsyntax to generate spvasm.vim, a SPIR-V
assembly syntax file for Vim.
- Version string: Allow overriding of wall clock timestamp with contents

View File

@ -109,6 +109,15 @@ Optimizer::PassToken CreateStripDebugInfoPass();
Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
const std::unordered_map<uint32_t, std::string>& id_value_map);
// Creates a flatten-decoration pass.
// A flatten-decoration pass replaces grouped decorations with equivalent
// ungrouped decorations. That is, it replaces each OpDecorationGroup
// instruction and associated OpGroupDecorate and OpGroupMemberDecorate
// instructions with equivalent OpDecorate and OpMemberDecorate instructions.
// The pass does not attempt to preserve debug information for instructions
// it removes.
Optimizer::PassToken CreateFlattenDecorationPass();
// Creates a freeze-spec-constant-value pass.
// A freeze-spec-constant pass specializes the value of spec constants to
// their default values. This pass only processes the spec constants that have

View File

@ -17,6 +17,7 @@ add_library(SPIRV-Tools-opt
constants.h
def_use_manager.h
eliminate_dead_constant_pass.h
flatten_decoration_pass.h
function.h
fold_spec_constant_op_and_composite_pass.h
freeze_spec_constant_value_pass.h
@ -40,6 +41,7 @@ add_library(SPIRV-Tools-opt
build_module.cpp
def_use_manager.cpp
eliminate_dead_constant_pass.cpp
flatten_decoration_pass.cpp
function.cpp
fold_spec_constant_op_and_composite_pass.cpp
freeze_spec_constant_value_pass.cpp

View File

@ -0,0 +1,164 @@
// 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 "flatten_decoration_pass.h"
#include <cassert>
#include <vector>
#include <unordered_map>
#include <unordered_set>
namespace spvtools {
namespace opt {
using ir::Instruction;
using ir::Operand;
using Words = std::vector<uint32_t>;
using OrderedUsesMap = std::unordered_map<uint32_t, Words>;
Pass::Status FlattenDecorationPass::Process(ir::Module* module) {
bool modified = false;
// The target Id of OpDecorationGroup instructions.
// We have to track this separately from its uses, in case it
// has no uses.
std::unordered_set<uint32_t> group_ids;
// Maps a decoration group Id to its GroupDecorate targets, in order
// of appearance.
OrderedUsesMap normal_uses;
// Maps a decoration group Id to its GroupMemberDecorate targets and
// their indices, in of appearance.
OrderedUsesMap member_uses;
auto annotations = module->annotations();
// On the first pass, record each OpDecorationGroup with its ordered uses.
// Rely on unordered_map::operator[] to create its entries on first access.
for (const auto& inst : annotations) {
switch (inst.opcode()) {
case SpvOp::SpvOpDecorationGroup:
group_ids.insert(inst.result_id());
break;
case SpvOp::SpvOpGroupDecorate: {
Words& words = normal_uses[inst.GetSingleWordInOperand(0)];
for (uint32_t i = 1; i < inst.NumInOperandWords(); i++) {
words.push_back(inst.GetSingleWordInOperand(i));
}
} break;
case SpvOp::SpvOpGroupMemberDecorate: {
Words& words = member_uses[inst.GetSingleWordInOperand(0)];
for (uint32_t i = 1; i < inst.NumInOperandWords(); i++) {
words.push_back(inst.GetSingleWordInOperand(i));
}
} break;
default:
break;
}
}
// On the second pass, replace OpDecorationGroup and its uses with
// equivalent normal and struct member uses.
auto inst_iter = annotations.begin();
// We have to re-evaluate the end pointer
while (inst_iter != module->annotations().end()) {
// Should we replace this instruction?
bool replace = false;
switch (inst_iter->opcode()) {
case SpvOp::SpvOpDecorationGroup:
case SpvOp::SpvOpGroupDecorate:
case SpvOp::SpvOpGroupMemberDecorate:
replace = true;
break;
case SpvOp::SpvOpDecorate: {
// If this decoration targets a group, then replace it
// by sets of normal and member decorations.
const uint32_t group = inst_iter->GetSingleWordOperand(0);
const auto normal_uses_iter = normal_uses.find(group);
if (normal_uses_iter != normal_uses.end()) {
for (auto target : normal_uses[group]) {
std::unique_ptr<Instruction> new_inst(new Instruction(*inst_iter));
new_inst->SetInOperand(0, Words{target});
inst_iter = inst_iter.InsertBefore(std::move(new_inst));
++inst_iter;
replace = true;
}
}
const auto member_uses_iter = member_uses.find(group);
if (member_uses_iter != member_uses.end()) {
const Words& member_id_pairs = (*member_uses_iter).second;
// The collection is a sequence of pairs.
assert((member_id_pairs.size() % 2) == 0);
for (size_t i = 0; i < member_id_pairs.size(); i += 2) {
// Make an OpMemberDecorate instruction for each (target, member)
// pair.
const uint32_t target = member_id_pairs[i];
const uint32_t member = member_id_pairs[i + 1];
std::vector<Operand> operands;
operands.push_back(Operand(SPV_OPERAND_TYPE_ID, {target}));
operands.push_back(
Operand(SPV_OPERAND_TYPE_LITERAL_INTEGER, {member}));
auto decoration_operands_iter = inst_iter->begin();
decoration_operands_iter++; // Skip the group target.
operands.insert(operands.end(), decoration_operands_iter,
inst_iter->end());
std::unique_ptr<Instruction> new_inst(
new Instruction(SpvOp::SpvOpMemberDecorate, 0, 0, operands));
inst_iter = inst_iter.InsertBefore(std::move(new_inst));
++inst_iter;
replace = true;
}
}
// If this is an OpDecorate targeting the OpDecorationGroup itself,
// remove it even if that decoration group itself is not the target of
// any OpGroupDecorate or OpGroupMemberDecorate.
if (!replace && group_ids.count(group)) {
replace = true;
}
} break;
default:
break;
}
if (replace) {
inst_iter = inst_iter.Erase();
modified = true;
} else {
// Handle the case of decorations unrelated to decoration groups.
++inst_iter;
}
}
// Remove OpName instructions which reference the removed group decorations.
// An OpDecorationGroup instruction might not have been used by an
// OpGroupDecorate or OpGroupMemberDecorate instruction.
if (!group_ids.empty()) {
for (auto debug_inst_iter = module->debug_begin();
debug_inst_iter != module->debug_end();) {
if (debug_inst_iter->opcode() == SpvOp::SpvOpName) {
const uint32_t target = debug_inst_iter->GetSingleWordOperand(0);
if (group_ids.count(target)) {
debug_inst_iter = debug_inst_iter.Erase();
modified = true;
} else {
++debug_inst_iter;
}
}
}
}
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
}
} // namespace opt
} // namespace spvtools

View File

@ -0,0 +1,34 @@
// 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.
#ifndef LIBSPIRV_OPT_FLATTEN_DECORATION_PASS_H_
#define LIBSPIRV_OPT_FLATTEN_DECORATION_PASS_H_
#include "module.h"
#include "pass.h"
namespace spvtools {
namespace opt {
// See optimizer.hpp for documentation.
class FlattenDecorationPass : public Pass {
public:
const char* name() const override { return "flatten-decoration"; }
Status Process(ir::Module*) override;
};
} // namespace opt
} // namespace spvtools
#endif // LIBSPIRV_OPT_FLATTEN_DECORATION_PASS_H_

View File

@ -101,6 +101,11 @@ Optimizer::PassToken CreateSetSpecConstantDefaultValuePass(
MakeUnique<opt::SetSpecConstantDefaultValuePass>(id_value_map));
}
Optimizer::PassToken CreateFlattenDecorationPass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::FlattenDecorationPass>());
}
Optimizer::PassToken CreateFreezeSpecConstantValuePass() {
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::FreezeSpecConstantValuePass>());

View File

@ -18,6 +18,7 @@
// A single header to include all passes.
#include "eliminate_dead_constant_pass.h"
#include "flatten_decoration_pass.h"
#include "fold_spec_constant_op_and_composite_pass.h"
#include "inline_pass.h"
#include "freeze_spec_constant_value_pass.h"

View File

@ -38,6 +38,11 @@ add_spvtools_unittest(TARGET pass_strip_debug_info
LIBS SPIRV-Tools-opt
)
add_spvtools_unittest(TARGET pass_flatten_decoration
SRCS flatten_decoration_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt
)
add_spvtools_unittest(TARGET pass_freeze_spec_const
SRCS freeze_spec_const_test.cpp pass_utils.cpp
LIBS SPIRV-Tools-opt

View File

@ -0,0 +1,234 @@
// Copyright (c) 2017 Valve Corporation
// Copyright (c) 2017 LunarG 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 <gmock/gmock.h>
#include "pass_fixture.h"
#include "pass_utils.h"
namespace {
using namespace spvtools;
// Returns the initial part of the assembly text for a valid
// SPIR-V module, including instructions prior to decorations.
std::string PreambleAssembly() {
return
R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %hue %saturation %value
OpName %main "main"
OpName %void_fn "void_fn"
OpName %hue "hue"
OpName %saturation "saturation"
OpName %value "value"
OpName %entry "entry"
OpName %Point "Point"
OpName %Camera "Camera"
)";
}
// Retuns types
std::string TypesAndFunctionsAssembly() {
return
R"(%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%float = OpTypeFloat 32
%Point = OpTypeStruct %float %float %float
%Camera = OpTypeStruct %float %float
%_ptr_Input_float = OpTypePointer Input %float
%hue = OpVariable %_ptr_Input_float Input
%saturation = OpVariable %_ptr_Input_float Input
%value = OpVariable %_ptr_Input_float Input
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";
}
struct FlattenDecorationCase {
// Names and decorations before the pass.
std::string input;
// Names and decorations after the pass.
std::string expected;
};
using FlattenDecorationTest =
PassTest<::testing::TestWithParam<FlattenDecorationCase>>;
TEST_P(FlattenDecorationTest, TransformsDecorations) {
const auto before =
PreambleAssembly() + GetParam().input + TypesAndFunctionsAssembly();
const auto after =
PreambleAssembly() + GetParam().expected + TypesAndFunctionsAssembly();
SinglePassRunAndCheck<opt::FlattenDecorationPass>(before, after, false, true);
}
INSTANTIATE_TEST_CASE_P(NoUses, FlattenDecorationTest,
::testing::ValuesIn(std::vector<FlattenDecorationCase>{
// No OpDecorationGroup
{"", ""},
// OpDecorationGroup without any uses, and
// no OpName.
{"%group = OpDecorationGroup\n", ""},
// OpDecorationGroup without any uses, and
// with OpName targeting it. Proves you must
// remove the names as well.
{"OpName %group \"group\"\n"
"%group = OpDecorationGroup\n",
""},
// OpDecorationGroup with decorations that
// target it, but no uses in OpGroupDecorate
// or OpGroupMemberDecorate instructions.
{"OpDecorate %group Flat\n"
"OpDecorate %group NoPerspective\n"
"%group = OpDecorationGroup\n",
""},
}), );
INSTANTIATE_TEST_CASE_P(OpGroupDecorate, FlattenDecorationTest,
::testing::ValuesIn(std::vector<FlattenDecorationCase>{
// One OpGroupDecorate
{"OpName %group \"group\"\n"
"OpDecorate %group Flat\n"
"OpDecorate %group NoPerspective\n"
"%group = OpDecorationGroup\n"
"OpGroupDecorate %group %hue %saturation\n",
"OpDecorate %hue Flat\n"
"OpDecorate %saturation Flat\n"
"OpDecorate %hue NoPerspective\n"
"OpDecorate %saturation NoPerspective\n"},
// Multiple OpGroupDecorate
{"OpName %group \"group\"\n"
"OpDecorate %group Flat\n"
"OpDecorate %group NoPerspective\n"
"%group = OpDecorationGroup\n"
"OpGroupDecorate %group %hue %value\n"
"OpGroupDecorate %group %saturation\n",
"OpDecorate %hue Flat\n"
"OpDecorate %value Flat\n"
"OpDecorate %saturation Flat\n"
"OpDecorate %hue NoPerspective\n"
"OpDecorate %value NoPerspective\n"
"OpDecorate %saturation NoPerspective\n"},
// Two group decorations, interleaved
{"OpName %group0 \"group0\"\n"
"OpName %group1 \"group1\"\n"
"OpDecorate %group0 Flat\n"
"OpDecorate %group1 NoPerspective\n"
"%group0 = OpDecorationGroup\n"
"%group1 = OpDecorationGroup\n"
"OpGroupDecorate %group0 %hue %value\n"
"OpGroupDecorate %group1 %saturation\n",
"OpDecorate %hue Flat\n"
"OpDecorate %value Flat\n"
"OpDecorate %saturation NoPerspective\n"},
// Decoration with operands
{"OpName %group \"group\"\n"
"OpDecorate %group Location 42\n"
"%group = OpDecorationGroup\n"
"OpGroupDecorate %group %hue %saturation\n",
"OpDecorate %hue Location 42\n"
"OpDecorate %saturation Location 42\n"},
}), );
INSTANTIATE_TEST_CASE_P(OpGroupMemberDecorate, FlattenDecorationTest,
::testing::ValuesIn(std::vector<FlattenDecorationCase>{
// One OpGroupMemberDecorate
{"OpName %group \"group\"\n"
"OpDecorate %group Flat\n"
"OpDecorate %group Offset 16\n"
"%group = OpDecorationGroup\n"
"OpGroupMemberDecorate %group %Point 1\n",
"OpMemberDecorate %Point 1 Flat\n"
"OpMemberDecorate %Point 1 Offset 16\n"},
// Multiple OpGroupMemberDecorate using the same
// decoration group.
{"OpName %group \"group\"\n"
"OpDecorate %group Flat\n"
"OpDecorate %group NoPerspective\n"
"OpDecorate %group Offset 8\n"
"%group = OpDecorationGroup\n"
"OpGroupMemberDecorate %group %Point 2\n"
"OpGroupMemberDecorate %group %Camera 1\n",
"OpMemberDecorate %Point 2 Flat\n"
"OpMemberDecorate %Camera 1 Flat\n"
"OpMemberDecorate %Point 2 NoPerspective\n"
"OpMemberDecorate %Camera 1 NoPerspective\n"
"OpMemberDecorate %Point 2 Offset 8\n"
"OpMemberDecorate %Camera 1 Offset 8\n"},
// Two groups of member decorations, interleaved.
// Decoration is with and without operands.
{"OpName %group0 \"group0\"\n"
"OpName %group1 \"group1\"\n"
"OpDecorate %group0 Flat\n"
"OpDecorate %group0 Offset 8\n"
"OpDecorate %group1 NoPerspective\n"
"OpDecorate %group1 Offset 16\n"
"%group0 = OpDecorationGroup\n"
"%group1 = OpDecorationGroup\n"
"OpGroupMemberDecorate %group0 %Point 0\n"
"OpGroupMemberDecorate %group1 %Point 2\n",
"OpMemberDecorate %Point 0 Flat\n"
"OpMemberDecorate %Point 0 Offset 8\n"
"OpMemberDecorate %Point 2 NoPerspective\n"
"OpMemberDecorate %Point 2 Offset 16\n"},
}), );
INSTANTIATE_TEST_CASE_P(UnrelatedDecorations, FlattenDecorationTest,
::testing::ValuesIn(std::vector<FlattenDecorationCase>{
// A non-group non-member decoration is untouched.
{"OpDecorate %hue Centroid\n"
"OpDecorate %saturation Flat\n",
"OpDecorate %hue Centroid\n"
"OpDecorate %saturation Flat\n"},
// A non-group member decoration is untouched.
{"OpMemberDecorate %Point 0 Offset 0\n"
"OpMemberDecorate %Point 1 Offset 4\n"
"OpMemberDecorate %Point 1 Flat\n",
"OpMemberDecorate %Point 0 Offset 0\n"
"OpMemberDecorate %Point 1 Offset 4\n"
"OpMemberDecorate %Point 1 Flat\n"},
// A non-group non-member decoration survives any
// replacement of group decorations.
{"OpName %group \"group\"\n"
"OpDecorate %group Flat\n"
"OpDecorate %hue Centroid\n"
"OpDecorate %group NoPerspective\n"
"%group = OpDecorationGroup\n"
"OpGroupDecorate %group %hue %saturation\n",
"OpDecorate %hue Flat\n"
"OpDecorate %saturation Flat\n"
"OpDecorate %hue Centroid\n"
"OpDecorate %hue NoPerspective\n"
"OpDecorate %saturation NoPerspective\n"},
// A non-group member decoration survives any
// replacement of group decorations.
{"OpDecorate %group Offset 0\n"
"OpDecorate %group Flat\n"
"OpMemberDecorate %Point 1 Offset 4\n"
"%group = OpDecorationGroup\n"
"OpGroupMemberDecorate %group %Point 0\n",
"OpMemberDecorate %Point 0 Offset 0\n"
"OpMemberDecorate %Point 0 Flat\n"
"OpMemberDecorate %Point 1 Offset 4\n"},
}), );
} // anonymous namespace

View File

@ -63,6 +63,9 @@ Options:
Remove the duplicated constants.
--inline-entry-points-all
Exhaustively inline all function calls in entry points
--flatten-decorations
Replace decoration groups with repeated OpDecorate and
OpMemberDecorate instructions.
-h, --help Print this help.
--version Display optimizer version information.
)",
@ -131,6 +134,8 @@ int main(int argc, char** argv) {
optimizer.RegisterPass(CreateFoldSpecConstantOpAndCompositePass());
} else if (0 == strcmp(cur_arg, "--unify-const")) {
optimizer.RegisterPass(CreateUnifyConstantPass());
} else if (0 == strcmp(cur_arg, "--flatten-decorations")) {
optimizer.RegisterPass(CreateFlattenDecorationPass());
} else if ('\0' == cur_arg[1]) {
// Setting a filename of "-" to indicate stdin.
if (!in_file) {