Initial implementation of if conversion
authorAlan Baker <alanbaker@google.com>
Tue, 16 Jan 2018 16:15:06 +0000 (11:15 -0500)
committerAlan Baker <alanbaker@google.com>
Thu, 25 Jan 2018 17:42:00 +0000 (09:42 -0800)
* Handles simple cases only
* Identifies phis in blocks with two predecessors and attempts to
convert the phi to an select
 * does not perform code motion currently so the converted values must
 dominate the join point (e.g. can't be defined in the branches)
 * limited for now to two predecessors, but can be extended to handle
 more cases
* Adding if conversion to -O and -Os

18 files changed:
Android.mk
include/spirv-tools/optimizer.hpp
source/opt/CMakeLists.txt
source/opt/basic_block.h
source/opt/dominator_analysis.cpp [new file with mode: 0644]
source/opt/dominator_analysis.h
source/opt/if_conversion.cpp [new file with mode: 0644]
source/opt/if_conversion.h [new file with mode: 0644]
source/opt/ir_builder.h
source/opt/optimizer.cpp
source/opt/passes.h
test/opt/CMakeLists.txt
test/opt/dominator_tree/CMakeLists.txt
test/opt/dominator_tree/common_dominators.cpp [new file with mode: 0644]
test/opt/if_conversion_test.cpp [new file with mode: 0644]
test/opt/ir_builder.cpp
tools/opt/opt.cpp
utils/check_copyright.py

index 70d3bb6..e28144c 100644 (file)
@@ -68,6 +68,7 @@ SPVTOOLS_OPT_SRC_FILES := \
                source/opt/dead_variable_elimination.cpp \
                source/opt/decoration_manager.cpp \
                source/opt/def_use_manager.cpp \
+               source/opt/dominator_analysis.cpp \
                source/opt/dominator_tree.cpp \
                source/opt/eliminate_dead_constant_pass.cpp \
                source/opt/eliminate_dead_functions_pass.cpp \
@@ -77,6 +78,7 @@ SPVTOOLS_OPT_SRC_FILES := \
                source/opt/fold_spec_constant_op_and_composite_pass.cpp \
                source/opt/freeze_spec_constant_value_pass.cpp \
                source/opt/function.cpp \
+               source/opt/if_conversion.cpp \
                source/opt/inline_pass.cpp \
                source/opt/inline_exhaustive_pass.cpp \
                source/opt/inline_opaque_pass.cpp \
index d1679ab..b1e2cfe 100644 (file)
@@ -482,6 +482,9 @@ Optimizer::PassToken CreateCCPPass();
 // Current workaround: Avoid OpUnreachable instructions in loops.
 Optimizer::PassToken CreateWorkaround1209Pass();
 
+// Creates a pass that converts if-then-else like assignments into OpSelect.
+Optimizer::PassToken CreateIfConversionPass();
+
 }  // namespace spvtools
 
 #endif  // SPIRV_TOOLS_OPTIMIZER_HPP_
index 8b2c584..8dfe229 100644 (file)
@@ -36,6 +36,7 @@ add_library(SPIRV-Tools-opt
   fold_spec_constant_op_and_composite_pass.h
   freeze_spec_constant_value_pass.h
   function.h
+  if_conversion.h
   inline_exhaustive_pass.h
   inline_opaque_pass.h
   inline_pass.h
@@ -87,6 +88,7 @@ add_library(SPIRV-Tools-opt
   dead_variable_elimination.cpp
   decoration_manager.cpp
   def_use_manager.cpp
+  dominator_analysis.cpp
   dominator_tree.cpp
   eliminate_dead_constant_pass.cpp
   eliminate_dead_functions_pass.cpp
@@ -96,6 +98,7 @@ add_library(SPIRV-Tools-opt
   fold_spec_constant_op_and_composite_pass.cpp
   freeze_spec_constant_value_pass.cpp
   function.cpp
+  if_conversion.cpp
   inline_exhaustive_pass.cpp
   inline_opaque_pass.cpp
   inline_pass.cpp
index ec32537..15d8cb9 100644 (file)
@@ -123,6 +123,12 @@ class BasicBlock {
   inline void ForEachPhiInst(const std::function<void(Instruction*)>& f,
                              bool run_on_debug_line_insts = false);
 
+  // Runs the given function |f| on each Phi instruction in this basic block,
+  // and optionally on the debug line instructions that might precede them. If
+  // |f| returns false, iteration is terminated and this function return false.
+  inline bool WhileEachPhiInst(const std::function<bool(Instruction*)>& f,
+                               bool run_on_debug_line_insts = false);
+
   // Runs the given function |f| on each label id of each successor block
   void ForEachSuccessorLabel(
       const std::function<void(const uint32_t)>& f) const;
@@ -135,12 +141,7 @@ class BasicBlock {
 
   // Returns true if this basic block has any Phi instructions.
   bool HasPhiInstructions() {
-    int count = 0;
-    ForEachPhiInst([&count](ir::Instruction*) {
-      ++count;
-      return;
-    });
-    return count > 0;
+    return !WhileEachPhiInst([](ir::Instruction*) { return false; });
   }
 
   // Return true if this block is a loop header block.
@@ -244,12 +245,23 @@ inline void BasicBlock::ForEachInst(
       run_on_debug_line_insts);
 }
 
-inline void BasicBlock::ForEachPhiInst(
-    const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) {
+inline bool BasicBlock::WhileEachPhiInst(
+    const std::function<bool(Instruction*)>& f, bool run_on_debug_line_insts) {
   for (auto& inst : insts_) {
     if (inst.opcode() != SpvOpPhi) break;
-    inst.ForEachInst(f, run_on_debug_line_insts);
+    if (!inst.WhileEachInst(f, run_on_debug_line_insts)) return false;
   }
+  return true;
+}
+
+inline void BasicBlock::ForEachPhiInst(
+    const std::function<void(Instruction*)>& f, bool run_on_debug_line_insts) {
+  WhileEachPhiInst(
+      [&f](Instruction* inst) {
+        f(inst);
+        return true;
+      },
+      run_on_debug_line_insts);
 }
 
 }  // namespace ir
diff --git a/source/opt/dominator_analysis.cpp b/source/opt/dominator_analysis.cpp
new file mode 100644 (file)
index 0000000..d483575
--- /dev/null
@@ -0,0 +1,41 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "dominator_analysis.h"
+
+#include <unordered_set>
+
+namespace spvtools {
+namespace opt {
+
+ir::BasicBlock* DominatorAnalysisBase::CommonDominator(
+    ir::BasicBlock* b1, ir::BasicBlock* b2) const {
+  if (!b1 || !b2) return nullptr;
+
+  std::unordered_set<ir::BasicBlock*> seen;
+  ir::BasicBlock* block = b1;
+  while (block && seen.insert(block).second) {
+    block = ImmediateDominator(block);
+  }
+
+  block = b2;
+  while (block && !seen.count(block)) {
+    block = ImmediateDominator(block);
+  }
+
+  return block;
+}
+
+}  // namespace opt
+}  // namespace spvtools
index 722a979..27410bf 100644 (file)
@@ -111,6 +111,10 @@ class DominatorAnalysisBase {
     tree_.Visit(func);
   }
 
+  // Returns the most immediate basic block that dominates both |b1| and |b2|.
+  // If there is no such basic block, nullptr is returned.
+  ir::BasicBlock* CommonDominator(ir::BasicBlock* b1, ir::BasicBlock* b2) const;
+
  protected:
   DominatorTree tree_;
 };
diff --git a/source/opt/if_conversion.cpp b/source/opt/if_conversion.cpp
new file mode 100644 (file)
index 0000000..5fefca6
--- /dev/null
@@ -0,0 +1,188 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "if_conversion.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status IfConversion::Process(ir::IRContext* c) {
+  InitializeProcessing(c);
+
+  bool modified = false;
+  std::vector<ir::Instruction*> to_kill;
+  for (auto& func : *get_module()) {
+    DominatorAnalysis* dominators =
+        context()->GetDominatorAnalysis(&func, *cfg());
+    for (auto& block : func) {
+      // Check if it is possible for |block| to have phis that can be
+      // transformed.
+      ir::BasicBlock* common = nullptr;
+      if (!CheckBlock(&block, dominators, &common)) continue;
+
+      // Get an insertion point.
+      auto iter = block.begin();
+      while (iter != block.end() && iter->opcode() == SpvOpPhi) {
+        ++iter;
+      }
+
+      InstructionBuilder<ir::IRContext::kAnalysisDefUse |
+                         ir::IRContext::kAnalysisInstrToBlockMapping>
+          builder(context(), &*iter);
+      block.ForEachPhiInst([this, &builder, &modified, &common, &to_kill,
+                            dominators, &block](ir::Instruction* phi) {
+        // This phi is not compatible, but subsequent phis might be.
+        if (!CheckType(phi->type_id())) return;
+
+        // We cannot transform cases where the phi is used by another phi in the
+        // same block due to instruction ordering restrictions.
+        // TODO(alan-baker): If all inappropriate uses could also be
+        // transformed, we could still remove this phi.
+        if (!CheckPhiUsers(phi, &block)) return;
+
+        // Identify the incoming values associated with the true and false
+        // branches. If |then_block| dominates |inc0| or if the true edge
+        // branches straight to this block and |common| is |inc0|, then |inc0|
+        // is on the true branch. Otherwise the |inc1| is on the true branch.
+        ir::BasicBlock* inc0 = GetIncomingBlock(phi, 0u);
+        ir::Instruction* branch = common->terminator();
+        uint32_t condition = branch->GetSingleWordInOperand(0u);
+        ir::BasicBlock* then_block =
+            GetBlock(branch->GetSingleWordInOperand(1u));
+        ir::Instruction* true_value = nullptr;
+        ir::Instruction* false_value = nullptr;
+        if ((then_block == &block && inc0 == common) ||
+            dominators->Dominates(then_block, inc0)) {
+          true_value = GetIncomingValue(phi, 0u);
+          false_value = GetIncomingValue(phi, 1u);
+        } else {
+          true_value = GetIncomingValue(phi, 1u);
+          false_value = GetIncomingValue(phi, 0u);
+        }
+
+        // If either incoming value is defined in a block that does not dominate
+        // this phi, then we cannot eliminate the phi with a select.
+        // TODO(alan-baker): Perform code motion where it makes sense to enable
+        // the transform in this case.
+        ir::BasicBlock* true_def_block = context()->get_instr_block(true_value);
+        if (true_def_block && !dominators->Dominates(true_def_block, &block))
+          return;
+
+        ir::BasicBlock* false_def_block =
+            context()->get_instr_block(false_value);
+        if (false_def_block && !dominators->Dominates(false_def_block, &block))
+          return;
+
+        analysis::Type* data_ty =
+            context()->get_type_mgr()->GetType(true_value->type_id());
+        if (analysis::Vector* vec_data_ty = data_ty->AsVector()) {
+          condition = SplatCondition(vec_data_ty, condition, &builder);
+        }
+
+        ir::Instruction* select = builder.AddSelect(phi->type_id(), condition,
+                                                    true_value->result_id(),
+                                                    false_value->result_id());
+        context()->ReplaceAllUsesWith(phi->result_id(), select->result_id());
+        to_kill.push_back(phi);
+        modified = true;
+
+        return;
+      });
+    }
+  }
+
+  for (auto inst : to_kill) {
+    context()->KillInst(inst);
+  }
+
+  return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
+}
+
+bool IfConversion::CheckBlock(ir::BasicBlock* block,
+                              DominatorAnalysis* dominators,
+                              ir::BasicBlock** common) {
+  const std::vector<uint32_t>& preds = cfg()->preds(block->id());
+
+  // TODO(alan-baker): Extend to more than two predecessors
+  if (preds.size() != 2) return false;
+
+  ir::BasicBlock* inc0 = context()->get_instr_block(preds[0]);
+  if (dominators->Dominates(block, inc0)) return false;
+
+  ir::BasicBlock* inc1 = context()->get_instr_block(preds[1]);
+  if (dominators->Dominates(block, inc1)) return false;
+
+  // All phis will have the same common dominator, so cache the result
+  // for this block. If there is no common dominator, then we cannot transform
+  // any phi in this basic block.
+  *common = dominators->CommonDominator(inc0, inc1);
+  if (!*common) return false;
+  ir::Instruction* branch = (*common)->terminator();
+  if (branch->opcode() != SpvOpBranchConditional) return false;
+
+  return true;
+}
+
+bool IfConversion::CheckPhiUsers(ir::Instruction* phi, ir::BasicBlock* block) {
+  return get_def_use_mgr()->WhileEachUser(phi, [block,
+                                                this](ir::Instruction* user) {
+    if (user->opcode() == SpvOpPhi && context()->get_instr_block(user) == block)
+      return false;
+    return true;
+  });
+}
+
+uint32_t IfConversion::SplatCondition(
+    analysis::Vector* vec_data_ty, uint32_t cond,
+    InstructionBuilder<ir::IRContext::kAnalysisDefUse |
+                       ir::IRContext::kAnalysisInstrToBlockMapping>* builder) {
+  // If the data inputs to OpSelect are vectors, the condition for
+  // OpSelect must be a boolean vector with the same number of
+  // components. So splat the condition for the branch into a vector
+  // type.
+  analysis::Bool bool_ty;
+  analysis::Vector bool_vec_ty(&bool_ty, vec_data_ty->element_count());
+  uint32_t bool_vec_id =
+      context()->get_type_mgr()->GetTypeInstruction(&bool_vec_ty);
+  std::vector<uint32_t> ids(vec_data_ty->element_count(), cond);
+  return builder->AddCompositeConstruct(bool_vec_id, ids)->result_id();
+}
+
+bool IfConversion::CheckType(uint32_t id) {
+  ir::Instruction* type = get_def_use_mgr()->GetDef(id);
+  SpvOp op = type->opcode();
+  if (spvOpcodeIsScalarType(op) || op == SpvOpTypePointer ||
+      op == SpvOpTypeVector)
+    return true;
+  return false;
+}
+
+ir::BasicBlock* IfConversion::GetBlock(uint32_t id) {
+  return context()->get_instr_block(get_def_use_mgr()->GetDef(id));
+}
+
+ir::BasicBlock* IfConversion::GetIncomingBlock(ir::Instruction* phi,
+                                               uint32_t predecessor) {
+  uint32_t in_index = 2 * predecessor + 1;
+  return GetBlock(phi->GetSingleWordInOperand(in_index));
+}
+
+ir::Instruction* IfConversion::GetIncomingValue(ir::Instruction* phi,
+                                                uint32_t predecessor) {
+  uint32_t in_index = 2 * predecessor;
+  return get_def_use_mgr()->GetDef(phi->GetSingleWordInOperand(in_index));
+}
+
+}  // namespace opt
+}  // namespace spvtools
diff --git a/source/opt/if_conversion.h b/source/opt/if_conversion.h
new file mode 100644 (file)
index 0000000..5d3a1ee
--- /dev/null
@@ -0,0 +1,78 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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_IF_CONVERSION_H_
+#define LIBSPIRV_OPT_IF_CONVERSION_H_
+
+#include "basic_block.h"
+#include "ir_builder.h"
+#include "pass.h"
+#include "types.h"
+
+namespace spvtools {
+namespace opt {
+
+// See optimizer.hpp for documentation.
+class IfConversion : public Pass {
+ public:
+  const char* name() const override { return "if-conversion"; }
+  Status Process(ir::IRContext* context) override;
+
+  ir::IRContext::Analysis GetPreservedAnalyses() override {
+    return ir::IRContext::kAnalysisDefUse |
+           ir::IRContext::kAnalysisDominatorAnalysis |
+           ir::IRContext::kAnalysisInstrToBlockMapping |
+           ir::IRContext::kAnalysisCFG;
+  }
+
+ private:
+  // Returns true if |id| is a valid type for use with OpSelect. OpSelect only
+  // allows scalars, vectors and pointers as valid inputs.
+  bool CheckType(uint32_t id);
+
+  // Returns the basic block containing |id|.
+  ir::BasicBlock* GetBlock(uint32_t id);
+
+  // Returns the basic block for the |predecessor|'th index predecessor of
+  // |phi|.
+  ir::BasicBlock* GetIncomingBlock(ir::Instruction* phi, uint32_t predecessor);
+
+  // Returns the instruction defining the |predecessor|'th index of |phi|.
+  ir::Instruction* GetIncomingValue(ir::Instruction* phi, uint32_t predecessor);
+
+  // Returns the id of a OpCompositeConstruct boolean vector. The composite has
+  // the same number of elements as |vec_data_ty| and each member is |cond|.
+  // |where| indicates the location in |block| to insert the composite
+  // construct. If necessary, this function will also construct the necessary
+  // type instructions for the boolean vector.
+  uint32_t SplatCondition(
+      analysis::Vector* vec_data_ty, uint32_t cond,
+      InstructionBuilder<ir::IRContext::kAnalysisDefUse |
+                         ir::IRContext::kAnalysisInstrToBlockMapping>* builder);
+
+  // Returns true if none of |phi|'s users are in |block|.
+  bool CheckPhiUsers(ir::Instruction* phi, ir::BasicBlock* block);
+
+  // Returns |false| if |block| is not appropriate to transform. Only
+  // transforms blocks with two predecessors. Neither incoming block can be
+  // dominated by |block|. Both predecessors must share a common dominator that
+  // is terminated by a conditional branch.
+  bool CheckBlock(ir::BasicBlock* block, DominatorAnalysis* dominators,
+                  ir::BasicBlock** common);
+};
+
+}  //  namespace opt
+}  //  namespace spvtools
+
+#endif  //  LIBSPIRV_OPT_IF_CONVERSION_H_
index 7d71204..2778473 100644 (file)
@@ -123,6 +123,37 @@ class InstructionBuilder {
     return AddInstruction(std::move(phi_inst));
   }
 
+  // Creates a select instruction.
+  // |type| must match the types of |true_value| and |false_value|. It is up to
+  // the caller to ensure that |cond| is a correct type (bool or vector of
+  // bool) for |type|.
+  ir::Instruction* AddSelect(uint32_t type, uint32_t cond, uint32_t true_value,
+                             uint32_t false_value) {
+    std::unique_ptr<ir::Instruction> select(new ir::Instruction(
+        GetContext(), SpvOpSelect, type, GetContext()->TakeNextId(),
+        std::initializer_list<ir::Operand>{
+            {SPV_OPERAND_TYPE_ID, {cond}},
+            {SPV_OPERAND_TYPE_ID, {true_value}},
+            {SPV_OPERAND_TYPE_ID, {false_value}}}));
+    return AddInstruction(std::move(select));
+  }
+
+  // Create a composite construct.
+  // |type| should be a composite type and the number of elements it has should
+  // match the size od |ids|.
+  ir::Instruction* AddCompositeConstruct(uint32_t type,
+                                         const std::vector<uint32_t>& ids) {
+    std::vector<ir::Operand> ops;
+    for (auto id : ids) {
+      ops.emplace_back(SPV_OPERAND_TYPE_ID,
+                       std::initializer_list<uint32_t>{id});
+    }
+    std::unique_ptr<ir::Instruction> construct(
+        new ir::Instruction(GetContext(), SpvOpCompositeConstruct, type,
+                            GetContext()->TakeNextId(), ops));
+    return AddInstruction(std::move(construct));
+  }
+
   // 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));
index f9f02d1..47ac3fd 100644 (file)
@@ -125,6 +125,8 @@ Optimizer& Optimizer::RegisterPerformancePasses() {
       .RegisterPass(CreateCCPPass())
       .RegisterPass(CreateAggressiveDCEPass())
       .RegisterPass(CreateDeadBranchElimPass())
+      .RegisterPass(CreateIfConversionPass())
+      .RegisterPass(CreateAggressiveDCEPass())
       .RegisterPass(CreateBlockMergePass())
       .RegisterPass(CreateInsertExtractElimPass())
       .RegisterPass(CreateRedundancyEliminationPass())
@@ -147,6 +149,8 @@ Optimizer& Optimizer::RegisterSizePasses() {
       .RegisterPass(CreateCCPPass())
       .RegisterPass(CreateAggressiveDCEPass())
       .RegisterPass(CreateDeadBranchElimPass())
+      .RegisterPass(CreateIfConversionPass())
+      .RegisterPass(CreateAggressiveDCEPass())
       .RegisterPass(CreateBlockMergePass())
       .RegisterPass(CreateInsertExtractElimPass())
       .RegisterPass(CreateRedundancyEliminationPass())
@@ -354,4 +358,9 @@ Optimizer::PassToken CreateWorkaround1209Pass() {
       MakeUnique<opt::Workaround1209>());
 }
 
+Optimizer::PassToken CreateIfConversionPass() {
+  return MakeUnique<Optimizer::PassToken::Impl>(
+      MakeUnique<opt::IfConversion>());
+}
+
 }  // namespace spvtools
index 523db86..fd34131 100644 (file)
@@ -30,6 +30,7 @@
 #include "flatten_decoration_pass.h"
 #include "fold_spec_constant_op_and_composite_pass.h"
 #include "freeze_spec_constant_value_pass.h"
+#include "if_conversion.h"
 #include "inline_exhaustive_pass.h"
 #include "inline_opaque_pass.h"
 #include "insert_extract_elim.h"
index b0af324..b7a7955 100644 (file)
@@ -267,6 +267,11 @@ add_spvtools_unittest(TARGET pass_workaround1209
   LIBS SPIRV-Tools-opt
 )
 
+add_spvtools_unittest(TARGET pass_if_conversion
+  SRCS if_conversion_test.cpp pass_utils.cpp
+  LIBS SPIRV-Tools-opt
+)
+
 add_spvtools_unittest(TARGET ir_builder
   SRCS ir_builder.cpp
   LIBS SPIRV-Tools-opt
index 22778e4..31a5f32 100644 (file)
@@ -72,3 +72,8 @@ add_spvtools_unittest(TARGET dominator_generated
          generated.cpp
     LIBS SPIRV-Tools-opt
 )
+
+add_spvtools_unittest(TARGET dominator_common_dominators
+    SRCS common_dominators.cpp
+    LIBS SPIRV-Tools-opt
+)
diff --git a/test/opt/dominator_tree/common_dominators.cpp b/test/opt/dominator_tree/common_dominators.cpp
new file mode 100644 (file)
index 0000000..612dc45
--- /dev/null
@@ -0,0 +1,151 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "opt/build_module.h"
+#include "opt/ir_context.h"
+
+namespace {
+
+using namespace spvtools;
+using CommonDominatorsTest = ::testing::Test;
+
+const std::string text = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %func "func"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%functy = OpTypeFunction %void
+%func = OpFunction %void None %functy
+%1 = OpLabel
+OpBranch %2
+%2 = OpLabel
+OpLoopMerge %3 %4 None
+OpBranch %5
+%5 = OpLabel
+OpBranchConditional %true %3 %4
+%4 = OpLabel
+OpBranch %2
+%3 = OpLabel
+OpSelectionMerge %6 None
+OpBranchConditional %true %7 %8
+%7 = OpLabel
+OpBranch %6
+%8 = OpLabel
+OpBranch %9
+%9 = OpLabel
+OpBranch %6
+%6 = OpLabel
+OpBranch %10
+%11 = OpLabel
+OpBranch %10
+%10 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ir::BasicBlock* GetBlock(uint32_t id, std::unique_ptr<ir::IRContext>& context) {
+  return context->get_instr_block(context->get_def_use_mgr()->GetDef(id));
+}
+
+TEST(CommonDominatorsTest, SameBlock) {
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, context);
+
+  ir::CFG cfg(context->module());
+  opt::DominatorAnalysis* analysis =
+      context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
+
+  for (auto& block : *context->module()->begin()) {
+    EXPECT_EQ(&block, analysis->CommonDominator(&block, &block));
+  }
+}
+
+TEST(CommonDominatorsTest, ParentAndChild) {
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, context);
+
+  ir::CFG cfg(context->module());
+  opt::DominatorAnalysis* analysis =
+      context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
+
+  EXPECT_EQ(
+      GetBlock(1u, context),
+      analysis->CommonDominator(GetBlock(1u, context), GetBlock(2u, context)));
+  EXPECT_EQ(
+      GetBlock(2u, context),
+      analysis->CommonDominator(GetBlock(2u, context), GetBlock(5u, context)));
+  EXPECT_EQ(
+      GetBlock(1u, context),
+      analysis->CommonDominator(GetBlock(1u, context), GetBlock(5u, context)));
+}
+
+TEST(CommonDominatorsTest, BranchSplit) {
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, context);
+
+  ir::CFG cfg(context->module());
+  opt::DominatorAnalysis* analysis =
+      context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
+
+  EXPECT_EQ(
+      GetBlock(3u, context),
+      analysis->CommonDominator(GetBlock(7u, context), GetBlock(8u, context)));
+  EXPECT_EQ(
+      GetBlock(3u, context),
+      analysis->CommonDominator(GetBlock(7u, context), GetBlock(9u, context)));
+}
+
+TEST(CommonDominatorsTest, LoopContinueAndMerge) {
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, context);
+
+  ir::CFG cfg(context->module());
+  opt::DominatorAnalysis* analysis =
+      context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
+
+  EXPECT_EQ(
+      GetBlock(5u, context),
+      analysis->CommonDominator(GetBlock(3u, context), GetBlock(4u, context)));
+}
+
+TEST(CommonDominatorsTest, NoCommonDominator) {
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text,
+                  SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  EXPECT_NE(nullptr, context);
+
+  ir::CFG cfg(context->module());
+  opt::DominatorAnalysis* analysis =
+      context->GetDominatorAnalysis(&*context->module()->begin(), cfg);
+
+  EXPECT_EQ(nullptr, analysis->CommonDominator(GetBlock(10u, context),
+                                               GetBlock(11u, context)));
+  EXPECT_EQ(nullptr, analysis->CommonDominator(GetBlock(11u, context),
+                                               GetBlock(6u, context)));
+}
+
+}  // anonymous namespace
diff --git a/test/opt/if_conversion_test.cpp b/test/opt/if_conversion_test.cpp
new file mode 100644 (file)
index 0000000..b311471
--- /dev/null
@@ -0,0 +1,332 @@
+// Copyright (c) 2018 Google LLC
+//
+// 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 "assembly_builder.h"
+#include "gmock/gmock.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+
+using IfConversionTest = PassTest<::testing::Test>;
+
+#ifdef SPIRV_EFFCEE
+TEST_F(IfConversionTest, TestSimpleIfThenElse) {
+  const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %16
+%15 = OpLabel
+OpBranch %14
+%16 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint %uint_0 %15 %uint_1 %16
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, TestSimpleHalfIfTrue) {
+  const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %14
+%15 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint %uint_0 %15 %uint_1 %12
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, TestSimpleHalfIfExtraBlock) {
+  const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %14
+%15 = OpLabel
+OpBranch %16
+%16 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint %uint_0 %15 %uint_1 %12
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, TestSimpleHalfIfFalse) {
+  const std::string text = R"(
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[sel:%\w+]] = OpSelect %uint %true %uint_0 %uint_1
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %14 %15
+%15 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint %uint_0 %12 %uint_1 %15
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::IfConversion>(text, true);
+}
+
+TEST_F(IfConversionTest, TestVectorSplat) {
+  const std::string text = R"(
+; CHECK: [[bool_vec:%\w+]] = OpTypeVector %bool 2
+; CHECK: OpSelectionMerge [[merge:%\w+]]
+; CHECK: [[merge]] = OpLabel
+; CHECK-NOT: OpPhi
+; CHECK: [[comp:%\w+]] = OpCompositeConstruct [[bool_vec]] %true %true
+; CHECK: [[sel:%\w+]] = OpSelect {{%\w+}} [[comp]]
+; CHECK OpStore {{%\w+}} [[sel]]
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%uint_vec2 = OpTypeVector %uint 2
+%vec2_01 = OpConstantComposite %uint_vec2 %uint_0 %uint_1
+%vec2_10 = OpConstantComposite %uint_vec2 %uint_1 %uint_0
+%_ptr_Output_uint = OpTypePointer Output %uint_vec2
+%2 = OpVariable %_ptr_Output_uint Output
+%11 = OpTypeFunction %void
+%1 = OpFunction %void None %11
+%12 = OpLabel
+OpSelectionMerge %14 None
+OpBranchConditional %true %15 %16
+%15 = OpLabel
+OpBranch %14
+%16 = OpLabel
+OpBranch %14
+%14 = OpLabel
+%18 = OpPhi %uint_vec2 %vec2_01 %15 %vec2_10 %16
+OpStore %2 %18
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndMatch<opt::IfConversion>(text, true);
+}
+#endif  // SPIRV_EFFCEE
+
+TEST_F(IfConversionTest, NoCommonDominator) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%1 = OpFunction %void None %8
+%9 = OpLabel
+OpBranch %10
+%11 = OpLabel
+OpBranch %10
+%10 = OpLabel
+%12 = OpPhi %uint %uint_0 %9 %uint_1 %11
+OpStore %2 %12
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
+}
+
+TEST_F(IfConversionTest, LoopUntouched) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%1 = OpFunction %void None %8
+%11 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%13 = OpPhi %uint %uint_0 %11 %uint_1 %12
+OpLoopMerge %14 %12 None
+OpBranchConditional %true %14 %12
+%14 = OpLabel
+OpStore %2 %13
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
+}
+
+TEST_F(IfConversionTest, TooManyPredecessors) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%1 = OpFunction %void None %8
+%11 = OpLabel
+OpSelectionMerge %12 None
+OpBranchConditional %true %13 %12
+%13 = OpLabel
+OpBranchConditional %true %14 %15
+%14 = OpLabel
+OpBranch %12
+%15 = OpLabel
+OpBranch %12
+%12 = OpLabel
+%16 = OpPhi %uint %uint_0 %11 %uint_0 %14 %uint_1 %15
+OpStore %2 %16
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
+}
+
+TEST_F(IfConversionTest, NoCodeMotion) {
+  const std::string text = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %1 "func" %2
+%void = OpTypeVoid
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Output_uint = OpTypePointer Output %uint
+%2 = OpVariable %_ptr_Output_uint Output
+%8 = OpTypeFunction %void
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%1 = OpFunction %void None %8
+%11 = OpLabel
+OpSelectionMerge %12 None
+OpBranchConditional %true %13 %12
+%13 = OpLabel
+%14 = OpIAdd %uint %uint_0 %uint_1
+OpBranch %12
+%12 = OpLabel
+%15 = OpPhi %uint %uint_0 %11 %14 %13
+OpStore %2 %15
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::IfConversion>(text, text, true, true);
+}
+
+}  // anonymous namespace
index f43d2d6..e8b15bb 100644 (file)
@@ -226,6 +226,75 @@ TEST_F(IRBuilderTest, TestCondBranchAddition) {
   }
 }
 
+TEST_F(IRBuilderTest, AddSelect) {
+  const std::string text = R"(
+; CHECK: [[bool:%\w+]] = OpTypeBool
+; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
+; CHECK: [[true:%\w+]] = OpConstantTrue [[bool]]
+; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1
+; CHECK: OpSelect [[uint]] [[true]] [[u0]] [[u1]]
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+%1 = OpTypeVoid
+%2 = OpTypeBool
+%3 = OpTypeInt 32 0
+%4 = OpConstantTrue %2
+%5 = OpConstant %3 0
+%6 = OpConstant %3 1
+%7 = OpTypeFunction %1
+%8 = OpFunction %1 None %7
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  EXPECT_NE(nullptr, context);
+
+  opt::InstructionBuilder<> builder(
+      context.get(), &*context->module()->begin()->begin()->begin());
+  EXPECT_NE(nullptr, builder.AddSelect(3u, 4u, 5u, 6u));
+
+  Match(text, context.get());
+}
+
+TEST_F(IRBuilderTest, AddCompositeConstruct) {
+  const std::string text = R"(
+; CHECK: [[uint:%\w+]] = OpTypeInt
+; CHECK: [[u0:%\w+]] = OpConstant [[uint]] 0
+; CHECK: [[u1:%\w+]] = OpConstant [[uint]] 1
+; CHECK: [[struct:%\w+]] = OpTypeStruct [[uint]] [[uint]] [[uint]] [[uint]]
+; CHECK: OpCompositeConstruct [[struct]] [[u0]] [[u1]] [[u1]] [[u0]]
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+%1 = OpTypeVoid
+%2 = OpTypeInt 32 0
+%3 = OpConstant %2 0
+%4 = OpConstant %2 1
+%5 = OpTypeStruct %2 %2 %2 %2
+%6 = OpTypeFunction %1
+%7 = OpFunction %1 None %6
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  std::unique_ptr<ir::IRContext> context =
+      BuildModule(SPV_ENV_UNIVERSAL_1_2, nullptr, text);
+  EXPECT_NE(nullptr, context);
+
+  opt::InstructionBuilder<> builder(
+      context.get(), &*context->module()->begin()->begin()->begin());
+  std::vector<uint32_t> ids = {3u, 4u, 4u, 3u};
+  EXPECT_NE(nullptr, builder.AddCompositeConstruct(5u, ids));
+
+  Match(text, context.get());
+}
+
 #endif  // SPIRV_EFFCEE
 
 }  // anonymous namespace
index dd5fea6..c007b0a 100644 (file)
@@ -148,6 +148,8 @@ Options (in lexicographical order):
   --freeze-spec-const
                Freeze the values of specialization constants to their default
                values.
+  --if-conversion
+               Convert if-then-else like assignments into OpSelect.
   --inline-entry-points-exhaustive
                Exhaustively inline all function calls in entry point call tree
                functions. Currently does not inline calls to functions with
@@ -391,6 +393,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer,
               "error: Expected a string of <spec id>:<default value> pairs.");
           return {OPT_STOP, 1};
         }
+      } else if (0 == strcmp(cur_arg, "--if-conversion")) {
+        optimizer->RegisterPass(CreateIfConversionPass());
       } else if (0 == strcmp(cur_arg, "--freeze-spec-const")) {
         optimizer->RegisterPass(CreateFreezeSpecConstantValuePass());
       } else if (0 == strcmp(cur_arg, "--inline-entry-points-exhaustive")) {
index de7bf47..85ea128 100755 (executable)
@@ -30,6 +30,7 @@ import sys
 AUTHORS = ['The Khronos Group Inc.',
            'LunarG Inc.',
            'Google Inc.',
+           'Google LLC',
            'Pierre Moreau']
 CURRENT_YEAR='2018'