Introduce an instruction builder helper class.
authorVictor Lomuller <victor@codeplay.com>
Thu, 18 Jan 2018 17:14:58 +0000 (17:14 +0000)
committerSteven Perron <stevenperron@google.com>
Fri, 19 Jan 2018 15:17:45 +0000 (10:17 -0500)
The class factorize the instruction building process.
Def-use manager analysis can be updated on the fly to maintain coherency.
To be updated to take into account more analysis.

source/opt/ir_builder.h [new file with mode: 0644]
source/opt/ir_context.h
test/opt/CMakeLists.txt
test/opt/ir_builder.cpp [new file with mode: 0644]

diff --git a/source/opt/ir_builder.h b/source/opt/ir_builder.h
new file mode 100644 (file)
index 0000000..7d71204
--- /dev/null
@@ -0,0 +1,180 @@
+// Copyright (c) 2018 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_IR_BUILDER_H_
+#define LIBSPIRV_OPT_IR_BUILDER_H_
+
+#include "opt/basic_block.h"
+#include "opt/instruction.h"
+#include "opt/ir_context.h"
+
+namespace spvtools {
+namespace opt {
+
+// In SPIR-V, ids are encoded as uint16_t, this id is guaranteed to be always
+// invalid.
+constexpr uint32_t kInvalidId = std::numeric_limits<uint32_t>::max();
+
+// Helper class to abstract instruction construction and insertion.
+// |AnalysesToPreserve| asks the InstructionBuilder to preserve requested
+// analyses.
+// Supported analyses:
+//   - Def-use analysis
+//   - Instruction to block analysis
+template <ir::IRContext::Analysis AnalysesToPreserve =
+              ir::IRContext::kAnalysisNone>
+class InstructionBuilder {
+  static_assert(!(AnalysesToPreserve &
+                  ~(ir::IRContext::kAnalysisDefUse |
+                    ir::IRContext::kAnalysisInstrToBlockMapping)),
+                "There some unsupported analyses");
+
+ public:
+  using InsertionPointTy = spvtools::ir::BasicBlock::iterator;
+
+  // Creates an InstructionBuilder, all new instructions will be inserted before
+  // the instruction |insert_before|.
+  InstructionBuilder(ir::IRContext* context, ir::Instruction* insert_before)
+      : InstructionBuilder(context, context->get_instr_block(insert_before),
+                           InsertionPointTy(insert_before)) {}
+
+  // Creates an InstructionBuilder, all new instructions will be inserted at the
+  // end of the basic block |parent_block|.
+  InstructionBuilder(ir::IRContext* context, ir::BasicBlock* parent_block)
+      : InstructionBuilder(context, parent_block, parent_block->end()) {}
+
+  // Creates a new selection merge instruction.
+  // The id |merge_id| is the merge basic block id.
+  ir::Instruction* AddSelectionMerge(
+      uint32_t merge_id,
+      uint32_t selection_control = SpvSelectionControlMaskNone) {
+    std::unique_ptr<ir::Instruction> new_branch_merge(new ir::Instruction(
+        GetContext(), SpvOpSelectionMerge, 0, 0,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_SELECTION_CONTROL,
+          {selection_control}}}));
+    return AddInstruction(std::move(new_branch_merge));
+  }
+
+  // Creates a new branch instruction to |label_id|.
+  // Note that the user must make sure the final basic block is
+  // well formed.
+  ir::Instruction* AddBranch(uint32_t label_id) {
+    std::unique_ptr<ir::Instruction> new_branch(new ir::Instruction(
+        GetContext(), SpvOpBranch, 0, 0,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
+    return AddInstruction(std::move(new_branch));
+  }
+
+  // Creates a new conditional instruction and the associated selection merge
+  // instruction if requested.
+  // The id |cond_id| is the id of the condition instruction, must be of
+  // type bool.
+  // The id |true_id| is the id of the basic block to branch to if the condition
+  // is true.
+  // The id |false_id| is the id of the basic block to branch to if the
+  // condition is false.
+  // The id |merge_id| is the id of the merge basic block for the selection
+  // merge instruction. If |merge_id| equals kInvalidId then no selection merge
+  // instruction will be created.
+  // The value |selection_control| is the selection control flag for the
+  // selection merge instruction.
+  // Note that the user must make sure the final basic block is
+  // well formed.
+  ir::Instruction* AddConditionalBranch(
+      uint32_t cond_id, uint32_t true_id, uint32_t false_id,
+      uint32_t merge_id = kInvalidId,
+      uint32_t selection_control = SpvSelectionControlMaskNone) {
+    if (merge_id != kInvalidId) {
+      AddSelectionMerge(merge_id, selection_control);
+    }
+    std::unique_ptr<ir::Instruction> new_branch(new ir::Instruction(
+        GetContext(), SpvOpBranchConditional, 0, 0,
+        {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {cond_id}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {true_id}},
+         {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {false_id}}}));
+    return AddInstruction(std::move(new_branch));
+  }
+
+  // Creates a phi instruction.
+  // The id |type| must be the id of the phi instruction's type.
+  // The vector |incomings| must be a sequence of pairs of <definition id,
+  // parent id>.
+  ir::Instruction* AddPhi(uint32_t type,
+                          const std::vector<uint32_t>& incomings) {
+    assert(incomings.size() % 2 == 0 && "A sequence of pairs is expected");
+    std::vector<ir::Operand> phi_ops;
+    for (size_t i = 0; i < incomings.size(); i++) {
+      phi_ops.push_back({SPV_OPERAND_TYPE_ID, {incomings[i]}});
+    }
+    std::unique_ptr<ir::Instruction> phi_inst(new ir::Instruction(
+        GetContext(), SpvOpPhi, type, GetContext()->TakeNextId(), phi_ops));
+    return AddInstruction(std::move(phi_inst));
+  }
+
+  // Inserts the new instruction before the insertion point.
+  ir::Instruction* AddInstruction(std::unique_ptr<ir::Instruction>&& insn) {
+    ir::Instruction* insn_ptr = &*insert_before_.InsertBefore(std::move(insn));
+    UpdateInstrToBlockMapping(insn_ptr);
+    UpdateDefUseMgr(insn_ptr);
+    return insn_ptr;
+  }
+
+  // Returns the insertion point iterator.
+  InsertionPointTy GetInsertPoint() { return insert_before_; }
+
+  // Returns the context which instructions are constructed for.
+  ir::IRContext* GetContext() const { return context_; }
+
+  // Returns the set of preserved analyses.
+  inline static constexpr ir::IRContext::Analysis GetPreservedAnalysis() {
+    return AnalysesToPreserve;
+  }
+
+ private:
+  InstructionBuilder(ir::IRContext* context, ir::BasicBlock* parent,
+                     InsertionPointTy insert_before)
+      : context_(context), parent_(parent), insert_before_(insert_before) {}
+
+  // Returns true if the users requested to update |analysis|.
+  inline static constexpr bool IsAnalysisUpdateRequested(
+      ir::IRContext::Analysis analysis) {
+    return AnalysesToPreserve & analysis;
+  }
+
+  // Updates the def/use manager if the user requested it. If he did not request
+  // an update, this function does nothing.
+  inline void UpdateDefUseMgr(ir::Instruction* insn) {
+    if (IsAnalysisUpdateRequested(ir::IRContext::kAnalysisDefUse))
+      GetContext()->get_def_use_mgr()->AnalyzeInstDefUse(insn);
+  }
+
+  // Updates the instruction to block analysis if the user requested it. If he
+  // did not request an update, this function does nothing.
+  inline void UpdateInstrToBlockMapping(ir::Instruction* insn) {
+    if (IsAnalysisUpdateRequested(
+            ir::IRContext::kAnalysisInstrToBlockMapping) &&
+        parent_)
+      GetContext()->set_instr_block(insn, parent_);
+  }
+
+  ir::IRContext* context_;
+  ir::BasicBlock* parent_;
+  InsertionPointTy insert_before_;
+};
+
+}  // namespace opt
+}  // namespace spvtools
+
+#endif  // LIBSPIRV_OPT_IR_BUILDER_H_
index 6602ddf..600ae61 100644 (file)
@@ -58,9 +58,9 @@ class IRContext {
     kAnalysisEnd = 1 << 6
   };
 
-  friend inline Analysis operator|(Analysis lhs, Analysis rhs);
+  friend inline constexpr Analysis operator|(Analysis lhs, Analysis rhs);
   friend inline Analysis& operator|=(Analysis& lhs, Analysis rhs);
-  friend inline Analysis operator<<(Analysis a, int shift);
+  friend inline constexpr Analysis operator<<(Analysis a, int shift);
   friend inline Analysis& operator<<=(Analysis& a, int shift);
 
   // Creates an |IRContext| that contains an owned |Module|
@@ -506,8 +506,8 @@ class IRContext {
   std::unique_ptr<opt::analysis::TypeManager> type_mgr_;
 };
 
-inline ir::IRContext::Analysis operator|(ir::IRContext::Analysis lhs,
-                                         ir::IRContext::Analysis rhs) {
+inline constexpr ir::IRContext::Analysis operator|(
+    ir::IRContext::Analysis lhs, ir::IRContext::Analysis rhs) {
   return static_cast<ir::IRContext::Analysis>(static_cast<int>(lhs) |
                                               static_cast<int>(rhs));
 }
@@ -519,8 +519,8 @@ inline ir::IRContext::Analysis& operator|=(ir::IRContext::Analysis& lhs,
   return lhs;
 }
 
-inline ir::IRContext::Analysis operator<<(ir::IRContext::Analysis a,
-                                          int shift) {
+inline constexpr ir::IRContext::Analysis operator<<(ir::IRContext::Analysis a,
+                                                    int shift) {
   return static_cast<ir::IRContext::Analysis>(static_cast<int>(a) << shift);
 }
 
index 04834bd..7f8f70f 100644 (file)
@@ -266,3 +266,8 @@ add_spvtools_unittest(TARGET pass_workaround1209
   SRCS workaround1209_test.cpp pass_utils.cpp
   LIBS SPIRV-Tools-opt
 )
+
+add_spvtools_unittest(TARGET ir_builder
+  SRCS ir_builder.cpp
+  LIBS SPIRV-Tools-opt
+)
diff --git a/test/opt/ir_builder.cpp b/test/opt/ir_builder.cpp
new file mode 100644 (file)
index 0000000..f43d2d6
--- /dev/null
@@ -0,0 +1,231 @@
+// Copyright (c) 2018 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <algorithm>
+#include <vector>
+
+#ifdef SPIRV_EFFCEE
+#include "effcee/effcee.h"
+#endif
+
+#include "opt/basic_block.h"
+#include "opt/ir_builder.h"
+
+#include "opt/build_module.h"
+#include "opt/instruction.h"
+#include "opt/type_manager.h"
+#include "spirv-tools/libspirv.hpp"
+
+namespace {
+
+using namespace spvtools;
+using ir::IRContext;
+using Analysis = IRContext::Analysis;
+
+#ifdef SPIRV_EFFCEE
+
+using IRBuilderTest = ::testing::Test;
+
+bool Validate(const std::vector<uint32_t>& bin) {
+  spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2;
+  spv_context spvContext = spvContextCreate(target_env);
+  spv_diagnostic diagnostic = nullptr;
+  spv_const_binary_t binary = {bin.data(), bin.size()};
+  spv_result_t error = spvValidate(spvContext, &binary, &diagnostic);
+  if (error != 0) spvDiagnosticPrint(diagnostic);
+  spvDiagnosticDestroy(diagnostic);
+  spvContextDestroy(spvContext);
+  return error == 0;
+}
+
+void Match(const std::string& original, ir::IRContext* context,
+           bool do_validation = true) {
+  std::vector<uint32_t> bin;
+  context->module()->ToBinary(&bin, true);
+  if (do_validation) {
+    EXPECT_TRUE(Validate(bin));
+  }
+  std::string assembly;
+  SpirvTools tools(SPV_ENV_UNIVERSAL_1_2);
+  EXPECT_TRUE(
+      tools.Disassemble(bin, &assembly, SpirvTools::kDefaultDisassembleOption))
+      << "Disassembling failed for shader:\n"
+      << assembly << std::endl;
+  auto match_result = effcee::Match(assembly, original);
+  EXPECT_EQ(effcee::Result::Status::Ok, match_result.status())
+      << match_result.message() << "\nChecking result:\n"
+      << assembly;
+}
+
+TEST_F(IRBuilderTest, TestInsnAddition) {
+  const std::string text = R"(
+; CHECK: %18 = OpLabel
+; CHECK: OpPhi %int %int_0 %14
+; CHECK: OpPhi %bool %16 %14
+; CHECK: OpBranch %17
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource GLSL 330
+               OpName %2 "main"
+               OpName %4 "i"
+               OpName %3 "c"
+               OpDecorate %3 Location 0
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeInt 32 1
+          %8 = OpTypePointer Function %7
+          %9 = OpConstant %7 0
+         %10 = OpTypeBool
+         %11 = OpTypeFloat 32
+         %12 = OpTypeVector %11 4
+         %13 = OpTypePointer Output %12
+          %3 = OpVariable %13 Output
+          %2 = OpFunction %5 None %6
+         %14 = OpLabel
+          %4 = OpVariable %8 Function
+               OpStore %4 %9
+         %15 = OpLoad %7 %4
+         %16 = OpINotEqual %10 %15 %9
+               OpSelectionMerge %17 None
+               OpBranchConditional %16 %18 %17
+         %18 = OpLabel
+               OpBranch %17
+         %17 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  {
+    std::unique_ptr<ir::IRContext> context =
+        BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+    ir::BasicBlock* bb = context->cfg()->block(18);
+
+    // Build managers.
+    context->get_def_use_mgr();
+    context->get_instr_block(nullptr);
+
+    opt::InstructionBuilder<> builder(context.get(), &*bb->begin());
+    ir::Instruction* phi1 = builder.AddPhi(7, {9, 14});
+    ir::Instruction* phi2 = builder.AddPhi(10, {16, 14});
+
+    // Make sure the InstructionBuilder did not update the def/use manager.
+    EXPECT_EQ(context->get_def_use_mgr()->GetDef(phi1->result_id()), nullptr);
+    EXPECT_EQ(context->get_def_use_mgr()->GetDef(phi2->result_id()), nullptr);
+    EXPECT_EQ(context->get_instr_block(phi1), nullptr);
+    EXPECT_EQ(context->get_instr_block(phi2), nullptr);
+
+    Match(text, context.get());
+  }
+
+  {
+    std::unique_ptr<ir::IRContext> context =
+        BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+    // Build managers.
+    context->get_def_use_mgr();
+    context->get_instr_block(nullptr);
+
+    ir::BasicBlock* bb = context->cfg()->block(18);
+    opt::InstructionBuilder<ir::IRContext::kAnalysisDefUse |
+                            ir::IRContext::kAnalysisInstrToBlockMapping>
+        builder(context.get(), &*bb->begin());
+    ir::Instruction* phi1 = builder.AddPhi(7, {9, 14});
+    ir::Instruction* phi2 = builder.AddPhi(10, {16, 14});
+
+    // Make sure InstructionBuilder updated the def/use manager
+    EXPECT_NE(context->get_def_use_mgr()->GetDef(phi1->result_id()), nullptr);
+    EXPECT_NE(context->get_def_use_mgr()->GetDef(phi2->result_id()), nullptr);
+    EXPECT_NE(context->get_instr_block(phi1), nullptr);
+    EXPECT_NE(context->get_instr_block(phi2), nullptr);
+
+    Match(text, context.get());
+  }
+}
+
+TEST_F(IRBuilderTest, TestCondBranchAddition) {
+  const std::string text = R"(
+; CHECK: %main = OpFunction %void None %6
+; CHECK-NEXT: %15 = OpLabel
+; CHECK-NEXT: OpSelectionMerge %13 None
+; CHECK-NEXT: OpBranchConditional %true %14 %13
+; CHECK-NEXT: %14 = OpLabel
+; CHECK-NEXT: OpBranch %13
+; CHECK-NEXT: %13 = OpLabel
+; CHECK-NEXT: OpReturn
+               OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %2 "main" %3
+               OpExecutionMode %2 OriginUpperLeft
+               OpSource GLSL 330
+               OpName %2 "main"
+               OpName %4 "i"
+               OpName %3 "c"
+               OpDecorate %3 Location 0
+          %5 = OpTypeVoid
+          %6 = OpTypeFunction %5
+          %7 = OpTypeBool
+          %8 = OpTypePointer Function %7
+          %9 = OpConstantTrue %7
+         %10 = OpTypeFloat 32
+         %11 = OpTypeVector %10 4
+         %12 = OpTypePointer Output %11
+          %3 = OpVariable %12 Output
+          %4 = OpVariable %8 Private
+          %2 = OpFunction %5 None %6
+         %13 = OpLabel
+               OpReturn
+               OpFunctionEnd
+)";
+
+  {
+    std::unique_ptr<ir::IRContext> context =
+        BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+
+    ir::Function& fn = *context->module()->begin();
+
+    ir::BasicBlock& bb_merge = *fn.begin();
+
+    fn.begin().InsertBefore(std::unique_ptr<ir::BasicBlock>(
+        new ir::BasicBlock(std::unique_ptr<ir::Instruction>(new ir::Instruction(
+            context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
+    ir::BasicBlock& bb_true = *fn.begin();
+    {
+      opt::InstructionBuilder<> builder(context.get(), &*bb_true.begin());
+      builder.AddBranch(bb_merge.id());
+    }
+
+    fn.begin().InsertBefore(std::unique_ptr<ir::BasicBlock>(
+        new ir::BasicBlock(std::unique_ptr<ir::Instruction>(new ir::Instruction(
+            context.get(), SpvOpLabel, 0, context->TakeNextId(), {})))));
+    ir::BasicBlock& bb_cond = *fn.begin();
+
+    opt::InstructionBuilder<> builder(context.get(), &bb_cond);
+    // This also test consecutive instruction insertion: merge selection +
+    // branch.
+    builder.AddConditionalBranch(9, bb_true.id(), bb_merge.id(), bb_merge.id());
+
+    Match(text, context.get());
+  }
+}
+
+#endif  // SPIRV_EFFCEE
+
+}  // anonymous namespace