Inline: Inline early return function if no returns in loop.
authorGreg Fischer <greg@lunarg.com>
Fri, 5 May 2017 02:55:53 +0000 (20:55 -0600)
committerDavid Neto <dneto@google.com>
Fri, 12 May 2017 21:18:00 +0000 (17:18 -0400)
source/opt/basic_block.h
source/opt/function.h
source/opt/inline_pass.cpp
source/opt/inline_pass.h
source/opt/instruction.h
source/opt/module.cpp
source/opt/module.h
test/opt/inline_test.cpp

index 6fb3a230441f13554bfaacb0071037010438c24a..73249051d992e278b058182208021e105192ee21 100644 (file)
@@ -48,7 +48,7 @@ class BasicBlock {
   Instruction& Label() { return *label_; }
 
   // Returns the id of the label at the top of this block
-  inline uint32_t label_id() const { return label_->result_id(); }
+  inline uint32_t id() const { return label_->result_id(); }
 
   iterator begin() { return iterator(&insts_, insts_.begin()); }
   iterator end() { return iterator(&insts_, insts_.end()); }
index 1c460d890b88ffe0d748bac221da083ed53a477a..949f99a5a4fa7be1056a027eeb2c2a70f4052172 100644 (file)
@@ -85,7 +85,7 @@ class Function {
   std::unique_ptr<Instruction> def_inst_;
   // All parameters to this function.
   std::vector<std::unique_ptr<Instruction>> params_;
-  // All basic blocks inside this function.
+  // All basic blocks inside this function in specification order
   std::vector<std::unique_ptr<BasicBlock>> blocks_;
   // The OpFunctionEnd instruction.
   std::unique_ptr<Instruction> end_inst_;
index 6211202be96eba22c0cb0ac62f0397b40b69f231..de55688d418bd8f8c8881af6bd6a1d4570a00e33 100644 (file)
@@ -15,6 +15,7 @@
 // limitations under the License.
 
 #include "inline_pass.h"
+#include "cfa.h"
 
 // Indices of operands in SPIR-V instructions
 
@@ -24,6 +25,8 @@ static const int kSpvFunctionCallArgumentId = 3;
 static const int kSpvReturnValueId = 0;
 static const int kSpvTypePointerStorageClass = 1;
 static const int kSpvTypePointerTypeId = 2;
+static const int kSpvLoopMergeMergeBlockId = 0;
+static const int kSpvSelectionMergeMergeBlockId = 0;
 
 namespace spvtools {
 namespace opt {
@@ -55,13 +58,33 @@ uint32_t InlinePass::AddPointerToType(uint32_t type_id,
 }
 
 void InlinePass::AddBranch(uint32_t label_id,
-                           std::unique_ptr<ir::BasicBlock>* block_ptr) {
+  std::unique_ptr<ir::BasicBlock>* block_ptr) {
+  std::unique_ptr<ir::Instruction> newBranch(new ir::Instruction(
+    SpvOpBranch, 0, 0,
+    {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
+  (*block_ptr)->AddInstruction(std::move(newBranch));
+}
+
+void InlinePass::AddBranchCond(uint32_t cond_id, uint32_t true_id,
+  uint32_t false_id, std::unique_ptr<ir::BasicBlock>* block_ptr) {
   std::unique_ptr<ir::Instruction> newBranch(new ir::Instruction(
-      SpvOpBranch, 0, 0,
-      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {label_id}}}));
+    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}}}));
   (*block_ptr)->AddInstruction(std::move(newBranch));
 }
 
+void InlinePass::AddLoopMerge(uint32_t merge_id, uint32_t continue_id,
+                           std::unique_ptr<ir::BasicBlock>* block_ptr) {
+  std::unique_ptr<ir::Instruction> newLoopMerge(new ir::Instruction(
+      SpvOpLoopMerge, 0, 0,
+      {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_ID, {continue_id}},
+       {spv_operand_type_t::SPV_OPERAND_TYPE_LOOP_CONTROL, {0}}}));
+  (*block_ptr)->AddInstruction(std::move(newLoopMerge));
+}
+
 void InlinePass::AddStore(uint32_t ptr_id, uint32_t val_id,
                           std::unique_ptr<ir::BasicBlock>* block_ptr) {
   std::unique_ptr<ir::Instruction> newStore(new ir::Instruction(
@@ -84,6 +107,22 @@ std::unique_ptr<ir::Instruction> InlinePass::NewLabel(uint32_t label_id) {
   return newLabel;
 }
 
+uint32_t InlinePass::GetFalseId() {
+  if (false_id_ != 0)
+    return false_id_;
+  false_id_ = module_->GetGlobalValue(SpvOpConstantFalse);
+  if (false_id_ != 0)
+    return false_id_;
+  uint32_t boolId = module_->GetGlobalValue(SpvOpTypeBool);
+  if (boolId == 0) {
+    boolId = TakeNextId();
+    module_->AddGlobalValue(SpvOpTypeBool, boolId, 0);
+  }
+  false_id_ = TakeNextId();
+  module_->AddGlobalValue(SpvOpConstantFalse, false_id_, boolId);
+  return false_id_;
+}
+
 void InlinePass::MapParams(
     ir::Function* calleeFn,
     ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
@@ -94,7 +133,7 @@ void InlinePass::MapParams(
         const uint32_t pid = cpi->result_id();
         (*callee2caller)[pid] = call_inst_itr->GetSingleWordOperand(
             kSpvFunctionCallArgumentId + param_idx);
-        param_idx++;
+        ++param_idx;
       });
 }
 
@@ -111,7 +150,7 @@ void InlinePass::CloneAndMapLocals(
     var_inst->SetResultId(newId);
     (*callee2caller)[callee_var_itr->result_id()] = newId;
     new_vars->push_back(std::move(var_inst));
-    callee_var_itr++;
+    ++callee_var_itr;
   }
 }
 
@@ -189,6 +228,10 @@ void InlinePass::GenInlineCode(
   ir::Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand(
       kSpvFunctionCallFunctionId)];
 
+  // Check for early returns
+  auto fi = early_return_.find(calleeFn->result_id());
+  bool earlyReturn = fi != early_return_.end();
+
   // Map parameters to actual arguments.
   MapParams(calleeFn, call_inst_itr, &callee2caller);
 
@@ -202,6 +245,8 @@ void InlinePass::GenInlineCode(
   // Clone and map callee code. Copy caller block code to beginning of
   // first block and end of last block.
   bool prevInstWasReturn = false;
+  uint32_t singleTripLoopHeaderId = 0;
+  uint32_t singleTripLoopContinueId = 0;
   uint32_t returnLabelId = 0;
   bool multiBlocks = false;
   const uint32_t calleeTypeId = calleeFn->type_id();
@@ -209,7 +254,9 @@ void InlinePass::GenInlineCode(
   calleeFn->ForEachInst([&new_blocks, &callee2caller, &call_block_itr,
                          &call_inst_itr, &new_blk_ptr, &prevInstWasReturn,
                          &returnLabelId, &returnVarId, &calleeTypeId,
-                         &multiBlocks, &postCallSB, &preCallSB, this](
+                         &multiBlocks, &postCallSB, &preCallSB, &earlyReturn,
+                         &singleTripLoopHeaderId, &singleTripLoopContinueId,
+                         this](
       const ir::Instruction* cpi) {
     switch (cpi->opcode()) {
       case SpvOpFunction:
@@ -239,7 +286,7 @@ void InlinePass::GenInlineCode(
         } else {
           // First block needs to use label of original block
           // but map callee label in case of phi reference.
-          labelId = call_block_itr->label_id();
+          labelId = call_block_itr->id();
           callee2caller[cpi->result_id()] = labelId;
           firstBlock = true;
         }
@@ -248,7 +295,7 @@ void InlinePass::GenInlineCode(
         if (firstBlock) {
           // Copy contents of original caller block up to call instruction.
           for (auto cii = call_block_itr->begin(); cii != call_inst_itr;
-               cii++) {
+               ++cii) {
             std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cii));
             // Remember same-block ops for possible regeneration.
             if (IsSameBlockOp(&*cp_inst)) {
@@ -257,6 +304,24 @@ void InlinePass::GenInlineCode(
             }
             new_blk_ptr->AddInstruction(std::move(cp_inst));
           }
+          // If callee is early return function, insert header block for
+          // one-trip loop that will encompass callee code. Start postheader
+          // block.
+          if (earlyReturn) {
+            singleTripLoopHeaderId = this->TakeNextId();
+            AddBranch(singleTripLoopHeaderId, &new_blk_ptr);
+            new_blocks->push_back(std::move(new_blk_ptr));
+            new_blk_ptr.reset(new ir::BasicBlock(NewLabel(
+                singleTripLoopHeaderId)));
+            returnLabelId = this->TakeNextId();
+            singleTripLoopContinueId = this->TakeNextId();
+            AddLoopMerge(returnLabelId, singleTripLoopContinueId, &new_blk_ptr);
+            uint32_t postHeaderId = this->TakeNextId();
+            AddBranch(postHeaderId, &new_blk_ptr);
+            new_blocks->push_back(std::move(new_blk_ptr));
+            new_blk_ptr.reset(new ir::BasicBlock(NewLabel(postHeaderId)));
+            multiBlocks = true;
+          }
         } else {
           multiBlocks = true;
         }
@@ -281,12 +346,17 @@ void InlinePass::GenInlineCode(
         prevInstWasReturn = true;
       } break;
       case SpvOpFunctionEnd: {
-        // If there was an early return, create return label/block.
+        // If there was an early return, insert continue and return blocks.
         // If previous instruction was return, insert branch instruction
         // to return block.
         if (returnLabelId != 0) {
           if (prevInstWasReturn) AddBranch(returnLabelId, &new_blk_ptr);
           new_blocks->push_back(std::move(new_blk_ptr));
+          new_blk_ptr.reset(new ir::BasicBlock(NewLabel(
+              singleTripLoopContinueId)));
+          AddBranchCond(GetFalseId(), singleTripLoopHeaderId, returnLabelId, 
+              &new_blk_ptr);
+          new_blocks->push_back(std::move(new_blk_ptr));
           new_blk_ptr.reset(new ir::BasicBlock(NewLabel(returnLabelId)));
           multiBlocks = true;
         }
@@ -298,7 +368,7 @@ void InlinePass::GenInlineCode(
         }
         // Copy remaining instructions from caller block.
         auto cii = call_inst_itr;
-        for (cii++; cii != call_block_itr->end(); cii++) {
+        for (++cii; cii != call_block_itr->end(); ++cii) {
           std::unique_ptr<ir::Instruction> cp_inst(new ir::Instruction(*cii));
           // If multiple blocks generated, regenerate any same-block
           // instruction that has not been seen in this last block.
@@ -322,7 +392,7 @@ void InlinePass::GenInlineCode(
           const auto mapItr = callee2caller.find(*iid);
           if (mapItr != callee2caller.end()) {
             *iid = mapItr->second;
-          } else if (cpi->has_labels()) {
+          } else if (cpi->HasLabels()) {
             const ir::Instruction* inst =
                 def_use_mgr_->id_to_defs().find(*iid)->second;
             if (inst->opcode() == SpvOpLabel) {
@@ -347,7 +417,7 @@ void InlinePass::GenInlineCode(
   });
   // Update block map given replacement blocks.
   for (auto& blk : *new_blocks) {
-    id2block_[blk->label_id()] = &*blk;
+    id2block_[blk->id()] = &*blk;
   }
 }
 
@@ -362,7 +432,7 @@ bool InlinePass::IsInlinableFunctionCall(const ir::Instruction* inst) {
 bool InlinePass::Inline(ir::Function* func) {
   bool modified = false;
   // Using block iterators here because of block erasures and insertions.
-  for (auto bi = func->begin(); bi != func->end(); bi++) {
+  for (auto bi = func->begin(); bi != func->end(); ++bi) {
     for (auto ii = bi->begin(); ii != bi->end();) {
       if (IsInlinableFunctionCall(&*ii)) {
         // Inline call.
@@ -374,8 +444,8 @@ bool InlinePass::Inline(ir::Function* func) {
         if (newBlocks.size() > 1) {
           const auto firstBlk = newBlocks.begin();
           const auto lastBlk = newBlocks.end() - 1;
-          const uint32_t firstId = (*firstBlk)->label_id();
-          const uint32_t lastId = (*lastBlk)->label_id();
+          const uint32_t firstId = (*firstBlk)->id();
+          const uint32_t lastId = (*lastBlk)->id();
           (*lastBlk)->ForEachSuccessorLabel(
               [&firstId, &lastId, this](uint32_t succ) {
                 ir::BasicBlock* sbp = this->id2block_[succ];
@@ -395,30 +465,132 @@ bool InlinePass::Inline(ir::Function* func) {
         ii = bi->begin();
         modified = true;
       } else {
-        ii++;
+        ++ii;
       }
     }
   }
   return modified;
 }
 
-bool InlinePass::IsInlinableFunction(const ir::Function* func) {
-  // We can only inline a function if it has blocks.
-  if (func->cbegin() == func->cend())
+bool InlinePass::HasMultipleReturns(ir::Function* func) {
+  bool seenReturn = false;
+  bool multipleReturns = false;
+  for (auto& blk : *func) {
+    auto terminal_ii = blk.cend();
+    --terminal_ii;
+    if (terminal_ii->opcode() == SpvOpReturn || 
+        terminal_ii->opcode() == SpvOpReturnValue) {
+      if (seenReturn) {
+        multipleReturns = true;
+        break;
+      }
+      seenReturn = true;
+    }
+  }
+  return multipleReturns;
+}
+
+uint32_t InlinePass::MergeBlockIdIfAny(const ir::BasicBlock& blk) {
+  auto merge_ii = blk.cend();
+  --merge_ii;
+  uint32_t mbid = 0;
+  if (merge_ii != blk.cbegin()) {
+    --merge_ii;
+    if (merge_ii->opcode() == SpvOpLoopMerge)
+      mbid = merge_ii->GetSingleWordOperand(kSpvLoopMergeMergeBlockId);
+    else if (merge_ii->opcode() == SpvOpSelectionMerge)
+      mbid = merge_ii->GetSingleWordOperand(kSpvSelectionMergeMergeBlockId);
+  }
+  return mbid;
+}
+
+void InlinePass::ComputeStructuredSuccessors(ir::Function* func) {
+  // If header, make merge block first successor.
+  for (auto& blk : *func) {
+    uint32_t mbid = MergeBlockIdIfAny(blk);
+    if (mbid != 0)
+      block2structured_succs_[&blk].push_back(id2block_[mbid]);
+    // add true successors
+    blk.ForEachSuccessorLabel([&blk, this](uint32_t sbid) {
+      block2structured_succs_[&blk].push_back(id2block_[sbid]);
+    });
+  }
+}
+
+InlinePass::GetBlocksFunction InlinePass::StructuredSuccessorsFunction() {
+  return [this](const ir::BasicBlock* block) {
+    return &(block2structured_succs_[block]);
+  };
+}
+
+bool InlinePass::HasNoReturnInLoop(ir::Function* func) {
+  // If control not structured, do not do loop/return analysis
+  // TODO: Analyze returns in non-structured control flow
+  if (!module_->HasCapability(SpvCapabilityShader))
     return false;
-  // Do not inline functions with multiple returns
-  // TODO(greg-lunarg): Enable inlining if no return is in loop
-  int returnCnt = 0;
-  for (auto bi = func->cbegin(); bi != func->cend(); bi++) {
-    auto li = bi->cend();
-    li--;
-    if (li->opcode() == SpvOpReturn || li->opcode() == SpvOpReturnValue) {
-      if (returnCnt > 0)
-        return false;
-      returnCnt++;
+  // Compute structured block order. This order has the property
+  // that dominators are before all blocks they dominate and merge blocks
+  // are after all blocks that are in the control constructs of their header.
+  ComputeStructuredSuccessors(func);
+  auto ignore_block = [](cbb_ptr) {};
+  auto ignore_edge = [](cbb_ptr, cbb_ptr) {};
+  std::list<const ir::BasicBlock*> structuredOrder;
+  spvtools::CFA<ir::BasicBlock>::DepthFirstTraversal(
+    &*func->begin(), StructuredSuccessorsFunction(), ignore_block,
+    [&](cbb_ptr b) { structuredOrder.push_front(b); }, ignore_edge);
+  // Search for returns in loops. Only need to track outermost loop
+  bool return_in_loop = false;
+  uint32_t outerLoopMergeId = 0;
+  for (auto& blk : structuredOrder) {
+    // Exiting current outer loop
+    if (blk->id() == outerLoopMergeId)
+      outerLoopMergeId = 0;
+    // Return block
+    auto terminal_ii = blk->cend();
+    --terminal_ii;
+    if (terminal_ii->opcode() == SpvOpReturn || 
+        terminal_ii->opcode() == SpvOpReturnValue) {
+      if (outerLoopMergeId != 0) {
+        return_in_loop = true;
+        break;
+      }
+    }
+    else if (terminal_ii != blk->cbegin()) {
+      auto merge_ii = terminal_ii;
+      --merge_ii;
+      // Entering outermost loop
+      if (merge_ii->opcode() == SpvOpLoopMerge && outerLoopMergeId == 0)
+        outerLoopMergeId = merge_ii->GetSingleWordOperand(
+            kSpvLoopMergeMergeBlockId);
     }
   }
-  return true;
+  return !return_in_loop;
+}
+
+void InlinePass::AnalyzeReturns(ir::Function* func) {
+  // Look for multiple returns
+  if (!HasMultipleReturns(func)) {
+    no_return_in_loop_.insert(func->result_id());
+    return;
+  }
+  early_return_.insert(func->result_id());
+  // If multiple returns, see if any are in a loop
+  if (HasNoReturnInLoop(func))
+    no_return_in_loop_.insert(func->result_id());
+}
+
+bool InlinePass::IsInlinableFunction(ir::Function* func) {
+  // We can only inline a function if it has blocks.
+  if (func->cbegin() == func->cend())
+    return false;
+  // Do not inline functions with returns in loops. Currently early return
+  // functions are inlined by wrapping them in a one trip loop and implementing
+  // the returns as a branch to the loop's merge block. However, this can only
+  // done validly if the return was not in a loop in the original function.
+  // Also remember functions with multiple (early) returns.
+  AnalyzeReturns(func);
+  const auto ci = no_return_in_loop_.find(func->result_id());
+  return ci != no_return_in_loop_.cend();
 }
 
 void InlinePass::Initialize(ir::Module* module) {
@@ -430,15 +602,19 @@ void InlinePass::Initialize(ir::Module* module) {
   // Save module.
   module_ = module;
 
-  // Initialize function and block maps.
+  false_id_ = 0;
+
   id2function_.clear();
   id2block_.clear();
+  block2structured_succs_.clear();
   inlinable_.clear();
   for (auto& fn : *module_) {
+    // Initialize function and block maps.
     id2function_[fn.result_id()] = &fn;
     for (auto& blk : fn) {
-      id2block_[blk.label_id()] = &blk;
+      id2block_[blk.id()] = &blk;
     }
+    // Compute inlinability
     if (IsInlinableFunction(&fn))
       inlinable_.insert(fn.result_id());
   }
index 2f549cd5de4740bb5021afca97b0adaf13489f5a..00d0e73fe720e1e764b7b33ea42945a691388c77 100644 (file)
@@ -21,6 +21,7 @@
 #include <memory>
 #include <unordered_map>
 #include <vector>
+#include <list>
 
 #include "def_use_manager.h"
 #include "module.h"
@@ -31,7 +32,13 @@ namespace opt {
 
 // See optimizer.hpp for documentation.
 class InlinePass : public Pass {
+
+  using cbb_ptr = const ir::BasicBlock*;
+
  public:
+   using GetBlocksFunction =
+     std::function<std::vector<ir::BasicBlock*>*(const ir::BasicBlock*)>;
+
   InlinePass();
   Status Process(ir::Module*) override;
 
@@ -56,6 +63,14 @@ class InlinePass : public Pass {
   // Add unconditional branch to labelId to end of block block_ptr.
   void AddBranch(uint32_t labelId, std::unique_ptr<ir::BasicBlock>* block_ptr);
 
+  // Add conditional branch to end of block |block_ptr|.
+  void AddBranchCond(uint32_t cond_id, uint32_t true_id,
+    uint32_t false_id, std::unique_ptr<ir::BasicBlock>* block_ptr);
+
+  // Add unconditional branch to labelId to end of block block_ptr.
+  void AddLoopMerge(uint32_t merge_id, uint32_t continue_id,
+                    std::unique_ptr<ir::BasicBlock>* block_ptr);
+
   // Add store of valId to ptrId to end of block block_ptr.
   void AddStore(uint32_t ptrId, uint32_t valId,
                 std::unique_ptr<ir::BasicBlock>* block_ptr);
@@ -67,6 +82,10 @@ class InlinePass : public Pass {
   // Return new label.
   std::unique_ptr<ir::Instruction> NewLabel(uint32_t label_id);
 
+  // Returns the id for the boolean false value. Looks in the module first
+  // and creates it if not found. Remembers it for future calls.
+  uint32_t GetFalseId();
+
   // Map callee params to caller args
   void MapParams(ir::Function* calleeFn,
                  ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
@@ -117,11 +136,39 @@ class InlinePass : public Pass {
                      ir::UptrVectorIterator<ir::Instruction> call_inst_itr,
                      ir::UptrVectorIterator<ir::BasicBlock> call_block_itr);
 
-  // Returns true if |inst| is a function call that can be inlined.
+  // Return true if |inst| is a function call that can be inlined.
   bool IsInlinableFunctionCall(const ir::Instruction* inst);
 
-  // Returns true if |func| is a function that can be inlined.
-  bool IsInlinableFunction(const ir::Function* func);
+  // Returns the id of the merge block declared by a merge instruction in 
+  // this block, if any.  If none, returns zero.
+  uint32_t MergeBlockIdIfAny(const ir::BasicBlock& blk);
+
+  // Compute structured successors for function |func|.
+  // A block's structured successors are the blocks it branches to
+  // together with its declared merge block if it has one.
+  // When order matters, the merge block always appears first.
+  // This assures correct depth first search in the presence of early 
+  // returns and kills. If the successor vector contain duplicates
+  // if the merge block, they are safely ignored by DFS.
+  void ComputeStructuredSuccessors(ir::Function* func);
+  
+  // Return function to return ordered structure successors for a given block
+  // Assumes ComputeStructuredSuccessors() has been called.
+  GetBlocksFunction StructuredSuccessorsFunction();
+
+  // Return true if |func| has multiple returns
+  bool HasMultipleReturns(ir::Function* func);
+
+  // Return true if |func| has no return in a loop. The current analysis
+  // requires structured control flow, so return false if control flow not
+  // structured ie. module is not a shader.
+  bool HasNoReturnInLoop(ir::Function* func);
+
+  // Find all functions with multiple returns and no returns in loops
+  void AnalyzeReturns(ir::Function* func);
+
+  // Return true if |func| is a function that can be inlined.
+  bool IsInlinableFunction(ir::Function* func);
 
   // Exhaustively inline all function calls in func as well as in
   // all code that is inlined into func. Return true if func is modified.
@@ -139,9 +186,23 @@ class InlinePass : public Pass {
   // Map from block's label id to block.
   std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
 
-  // Set of ids of inlinable function 
+  // Set of ids of functions with early returns
+  std::set<uint32_t> early_return_;
+
+  // Set of ids of functions with no returns in loop
+  std::set<uint32_t> no_return_in_loop_;
+
+  // Set of ids of inlinable functions
   std::set<uint32_t> inlinable_;
 
+  // Map from block to its structured successor blocks. See 
+  // ComputeStructuredSuccessors() for definition.
+  std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
+      block2structured_succs_;
+
+  // result id for OpConstantFalse
+  uint32_t false_id_;
+
   // Next unused ID
   uint32_t next_id_;
 };
index 2c3189cd1d94a9a438a6e8760eed27149ab09f14..636fc0fa5195889084d0f898cb4badb3812353cc 100644 (file)
@@ -171,7 +171,7 @@ class Instruction {
   inline void ForEachInId(const std::function<void(const uint32_t*)>& f) const;
 
   // Returns true if any operands can be labels
-  inline bool has_labels() const;
+  inline bool HasLabels() const;
 
   // Pushes the binary segments for this instruction into the back of *|binary|.
   void ToBinaryWithoutAttachedDebugInsts(std::vector<uint32_t>* binary) const;
@@ -257,7 +257,7 @@ inline void Instruction::ForEachInId(
     if (opnd.type == SPV_OPERAND_TYPE_ID) f(&opnd.words[0]);
 }
 
-inline bool Instruction::has_labels() const {
+inline bool Instruction::HasLabels() const {
   switch (opcode_) {
     case SpvOpSelectionMerge:
     case SpvOpBranch:
index 372b70ce89f476eaa2c6b38ae8bada53483a314c..b5f5f0e7c2cd04812b64e0d7beac7f324c6cc5e8 100644 (file)
@@ -58,6 +58,21 @@ std::vector<const Instruction*> Module::GetConstants() const {
   return insts;
 };
 
+uint32_t Module::GetGlobalValue(SpvOp opcode) const {
+  for (uint32_t i = 0; i < types_values_.size(); ++i) {
+    if (types_values_[i]->opcode() == opcode)
+      return types_values_[i]->result_id();
+  }
+  return 0;
+}
+
+void Module::AddGlobalValue(SpvOp opcode, uint32_t result_id,
+                            uint32_t type_id) {
+  std::unique_ptr<ir::Instruction> newGlobal(new ir::Instruction(
+    opcode, type_id, result_id, {}));
+  AddGlobalValue(std::move(newGlobal));
+}
+
 void Module::ForEachInst(const std::function<void(Instruction*)>& f,
                          bool run_on_debug_line_insts) {
 #define DELEGATE(i) i->ForEachInst(f, run_on_debug_line_insts)
@@ -123,5 +138,15 @@ uint32_t Module::ComputeIdBound() const {
   return highest + 1;
 }
 
+bool Module::HasCapability(uint32_t cap) {
+  for (auto& ci : capabilities_) {
+    uint32_t tcap = ci->GetSingleWordOperand(0);
+    if (tcap == cap) {
+      return true;
+    }
+  }
+  return false;
+}
+
 }  // namespace ir
 }  // namespace spvtools
index 5d98ddcda59035204d8937ce6f3bd314591e6b80..37d49025184b13d8db6dd025c76774fd9b5d2169 100644 (file)
@@ -86,6 +86,12 @@ class Module {
   std::vector<Instruction*> GetConstants();
   std::vector<const Instruction*> GetConstants() const;
 
+  // Return result id of global value with |opcode|, 0 if not present.
+  uint32_t GetGlobalValue(SpvOp opcode) const;
+
+  // Add global value with |opcode|, |result_id| and |type_id|
+  void AddGlobalValue(SpvOp opcode, uint32_t result_id, uint32_t type_id);
+
   inline uint32_t id_bound() const { return header_.bound; }
 
   // Iterators for debug instructions (excluding OpLine & OpNoLine) contained in
@@ -132,6 +138,9 @@ class Module {
   // Returns 1 more than the maximum Id value mentioned in the module.
   uint32_t ComputeIdBound() const;
 
+  // Returns true if module has capability |cap|
+  bool HasCapability(uint32_t cap);
+
  private:
   ModuleHeader header_;  // Module header
 
index 598bc389cb5862d1d5615191a168e57f77603e43..30df255621e1ff82ea968af59cb176b5c4c43a6b 100644 (file)
@@ -1358,7 +1358,7 @@ TEST_F(InlineTest, OpImageAndOpSampledImageOutOfBlock) {
       /* skip_nop = */ false, /* do_validate = */ true);
 }
 
-TEST_F(InlineTest, EarlyReturnFunctionIsNotInlined) {
+TEST_F(InlineTest, EarlyReturnFunctionInlined) {
   // #version 140
   // 
   // in vec4 BaseColor;
@@ -1376,7 +1376,7 @@ TEST_F(InlineTest, EarlyReturnFunctionIsNotInlined) {
   //     gl_FragColor = color; 
   // }
 
-  const std::string assembly =
+  const std::string predefs =
       R"(OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -1405,7 +1405,28 @@ OpName %gl_FragColor "gl_FragColor"
 %BaseColor = OpVariable %_ptr_Input_v4float Input
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %gl_FragColor = OpVariable %_ptr_Output_v4float Output
-%main = OpFunction %void None %10
+)";
+
+  const std::string nonEntryFuncs =
+      R"(%foo_vf4_ = OpFunction %float None %14
+%bar = OpFunctionParameter %_ptr_Function_v4float
+%27 = OpLabel
+%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%29 = OpLoad %float %28
+%30 = OpFOrdLessThan %bool %29 %float_0
+OpSelectionMerge %31 None
+OpBranchConditional %30 %32 %31
+%32 = OpLabel
+OpReturnValue %float_0
+%31 = OpLabel
+%33 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%34 = OpLoad %float %33
+OpReturnValue %34
+OpFunctionEnd
+)";
+
+  const std::string before =
+      R"(%main = OpFunction %void None %10
 %22 = OpLabel
 %color = OpVariable %_ptr_Function_v4float Function
 %param = OpVariable %_ptr_Function_v4float Function
@@ -1418,20 +1439,139 @@ OpStore %color %25
 OpStore %gl_FragColor %26
 OpReturn
 OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(%false = OpConstantFalse %bool
+%main = OpFunction %void None %10
+%22 = OpLabel
+%35 = OpVariable %_ptr_Function_float Function
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%23 = OpLoad %v4float %BaseColor
+OpStore %param %23
+OpBranch %36
+%36 = OpLabel
+OpLoopMerge %37 %38 None
+OpBranch %39
+%39 = OpLabel
+%40 = OpAccessChain %_ptr_Function_float %param %uint_0
+%41 = OpLoad %float %40
+%42 = OpFOrdLessThan %bool %41 %float_0
+OpSelectionMerge %43 None
+OpBranchConditional %42 %44 %43
+%44 = OpLabel
+OpStore %35 %float_0
+OpBranch %37
+%43 = OpLabel
+%45 = OpAccessChain %_ptr_Function_float %param %uint_0
+%46 = OpLoad %float %45
+OpStore %35 %46
+OpBranch %37
+%38 = OpLabel
+OpBranchConditional %false %36 %37
+%37 = OpLabel
+%24 = OpLoad %float %35
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %color %25
+%26 = OpLoad %v4float %color
+OpStore %gl_FragColor %26
+OpReturn
+OpFunctionEnd
+)";
+
+  SinglePassRunAndCheck<opt::InlinePass>(predefs + before + nonEntryFuncs, 
+      predefs + after + nonEntryFuncs, false, true);
+}
+TEST_F(InlineTest, EarlyReturnInLoopIsNotInlined) {
+  // #version 140
+  // 
+  // in vec4 BaseColor;
+  // 
+  // float foo(vec4 bar)
+  // {
+  //     while (true) {
+  //         if (bar.x < 0.0)
+  //             return 0.0;
+  //         return bar.x;
+  //     }
+  // }
+  // 
+  // void main()
+  // {
+  //     vec4 color = vec4(foo(BaseColor));
+  //     gl_FragColor = color; 
+  // }
+
+  const std::string assembly =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %gl_FragColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 140
+OpName %main "main"
+OpName %foo_vf4_ "foo(vf4;"
+OpName %bar "bar"
+OpName %color "color"
+OpName %BaseColor "BaseColor"
+OpName %param "param"
+OpName %gl_FragColor "gl_FragColor"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%14 = OpTypeFunction %float %_ptr_Function_v4float
+%bool = OpTypeBool
+%true = OpConstantTrue %bool
+%uint = OpTypeInt 32 0
+%uint_0 = OpConstant %uint 0
+%_ptr_Function_float = OpTypePointer Function %float
+%float_0 = OpConstant %float 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+%main = OpFunction %void None %10
+%23 = OpLabel
+%color = OpVariable %_ptr_Function_v4float Function
+%param = OpVariable %_ptr_Function_v4float Function
+%24 = OpLoad %v4float %BaseColor
+OpStore %param %24
+%25 = OpFunctionCall %float %foo_vf4_ %param
+%26 = OpCompositeConstruct %v4float %25 %25 %25 %25
+OpStore %color %26
+%27 = OpLoad %v4float %color
+OpStore %gl_FragColor %27
+OpReturn
+OpFunctionEnd
 %foo_vf4_ = OpFunction %float None %14
 %bar = OpFunctionParameter %_ptr_Function_v4float
-%27 = OpLabel
-%28 = OpAccessChain %_ptr_Function_float %bar %uint_0
-%29 = OpLoad %float %28
-%30 = OpFOrdLessThan %bool %29 %float_0
-OpSelectionMerge %31 None
-OpBranchConditional %30 %32 %31
+%28 = OpLabel
+OpBranch %29
+%29 = OpLabel
+OpLoopMerge %30 %31 None
+OpBranch %32
 %32 = OpLabel
+OpBranchConditional %true %33 %30
+%33 = OpLabel
+%34 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%35 = OpLoad %float %34
+%36 = OpFOrdLessThan %bool %35 %float_0
+OpSelectionMerge %37 None
+OpBranchConditional %36 %38 %37
+%38 = OpLabel
 OpReturnValue %float_0
+%37 = OpLabel
+%39 = OpAccessChain %_ptr_Function_float %bar %uint_0
+%40 = OpLoad %float %39
+OpReturnValue %40
 %31 = OpLabel
-%33 = OpAccessChain %_ptr_Function_float %bar %uint_0
-%34 = OpLoad %float %33
-OpReturnValue %34
+OpBranch %29
+%30 = OpLabel
+%41 = OpUndef %float
+OpReturnValue %41
 OpFunctionEnd
 )";