--- /dev/null
+// 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
--- /dev/null
+// 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