Adding additional functionality to ADCE.
authorAlan Baker <alanbaker@google.com>
Mon, 18 Dec 2017 17:13:10 +0000 (12:13 -0500)
committerAlan Baker <alanbaker@google.com>
Wed, 10 Jan 2018 13:35:48 +0000 (08:35 -0500)
Modified ADCE to remove dead globals.
* Entry point and execution mode instructions are marked as alive
* Reachable functions and their parameters are marked as alive
* Instruction deletion now deferred until the end of the pass
* Eliminated dead insts set, added IsDead to calculate that value
instead
* Ported applicable dead variable elimination tests
* Ported dead constant elim tests

Added dead function elimination to ADCE
* ported dead function elim tests

Added handling of decoration groups in ADCE
* Uses a custom sorter to traverse decorations in a specific order
* Simplifies necessary checks

Updated -O and -Os pass lists.

include/spirv-tools/optimizer.hpp
source/opt/aggressive_dead_code_elim_pass.cpp
source/opt/aggressive_dead_code_elim_pass.h
source/opt/optimizer.cpp
test/opt/aggressive_dead_code_elim_test.cpp
test/opt/assembly_builder.h

index c6f74c5..e1aac11 100644 (file)
@@ -385,7 +385,7 @@ Optimizer::PassToken CreateInsertExtractElimPass();
 Optimizer::PassToken CreateCommonUniformElimPass();
 
 // Create aggressive dead code elimination pass
-// This pass eliminates unused code from functions. In addition,
+// This pass eliminates unused code from the module. In addition,
 // it detects and eliminates code which may have spurious uses but which do
 // not contribute to the output of the function. The most common cause of
 // such code sequences is summations in loops whose result is no longer used
@@ -393,8 +393,9 @@ Optimizer::PassToken CreateCommonUniformElimPass();
 // time cost over standard dead code elimination.
 //
 // This pass only processes entry point functions. It also only processes
-// shaders with relaxed logical addressing (see opt/instruction.h). It currently
-// will not process functions with function calls.
+// shaders with relaxed logical addressing (see opt/instruction.h). It
+// currently will not process functions with function calls. Unreachable
+// functions are deleted.
 //
 // This pass will be made more effective by first running passes that remove
 // dead control flow and inlines function calls.
index efc34b5..6ba59ee 100644 (file)
@@ -19,6 +19,7 @@
 #include "cfa.h"
 #include "iterator.h"
 #include "latest_version_glsl_std_450_header.h"
+#include "reflect.h"
 
 #include <stack>
 
@@ -33,6 +34,57 @@ const uint32_t kSelectionMergeMergeBlockIdInIdx = 0;
 const uint32_t kLoopMergeMergeBlockIdInIdx = 0;
 const uint32_t kLoopMergeContinueBlockIdInIdx = 1;
 
+// Sorting functor to present annotation instructions in an easy-to-process
+// order. The functor orders by opcode first and falls back on unique id
+// ordering if both instructions have the same opcode.
+//
+// Desired priority:
+// SpvOpGroupDecorate
+// SpvOpGroupMemberDecorate
+// SpvOpDecorate
+// SpvOpMemberDecorate
+// SpvOpDecorateId
+// SpvOpDecorationGroup
+struct DecorationLess {
+  bool operator()(const ir::Instruction* lhs,
+                  const ir::Instruction* rhs) const {
+    assert(lhs && rhs);
+    SpvOp lhsOp = lhs->opcode();
+    SpvOp rhsOp = rhs->opcode();
+    if (lhsOp != rhsOp) {
+      // OpGroupDecorate and OpGroupMember decorate are highest priority to
+      // eliminate dead targets early and simplify subsequent checks.
+      if (lhsOp == SpvOpGroupDecorate && rhsOp != SpvOpGroupDecorate)
+        return true;
+      if (rhsOp == SpvOpGroupDecorate && lhsOp != SpvOpGroupDecorate)
+        return false;
+      if (lhsOp == SpvOpGroupMemberDecorate &&
+          rhsOp != SpvOpGroupMemberDecorate)
+        return true;
+      if (rhsOp == SpvOpGroupMemberDecorate &&
+          lhsOp != SpvOpGroupMemberDecorate)
+        return false;
+      if (lhsOp == SpvOpDecorate && rhsOp != SpvOpDecorate) return true;
+      if (rhsOp == SpvOpDecorate && lhsOp != SpvOpDecorate) return false;
+      if (lhsOp == SpvOpMemberDecorate && rhsOp != SpvOpMemberDecorate)
+        return true;
+      if (rhsOp == SpvOpMemberDecorate && lhsOp != SpvOpMemberDecorate)
+        return false;
+      if (lhsOp == SpvOpDecorateId && rhsOp != SpvOpDecorateId) return true;
+      if (rhsOp == SpvOpDecorateId && lhsOp != SpvOpDecorateId) return false;
+      // OpDecorationGroup is lowest priority to ensure use/def chains remain
+      // usable for instructions that target this group.
+      if (lhsOp == SpvOpDecorationGroup && rhsOp != SpvOpDecorationGroup)
+        return true;
+      if (rhsOp == SpvOpDecorationGroup && lhsOp != SpvOpDecorationGroup)
+        return false;
+    }
+
+    // Fall back to maintain total ordering (compare unique ids).
+    return *lhs < *rhs;
+  }
+};
+
 }  // namespace
 
 bool AggressiveDCEPass::IsVarOfStorage(uint32_t varId, uint32_t storageClass) {
@@ -82,13 +134,32 @@ bool AggressiveDCEPass::AllExtensionsSupported() const {
   return true;
 }
 
+bool AggressiveDCEPass::IsDead(ir::Instruction* inst) {
+  if (IsLive(inst)) return false;
+  if (inst->IsBranch() &&
+      !IsStructuredIfOrLoopHeader(context()->get_instr_block(inst), nullptr,
+                                  nullptr, nullptr))
+    return false;
+  return true;
+}
+
 bool AggressiveDCEPass::IsTargetDead(ir::Instruction* inst) {
   const uint32_t tId = inst->GetSingleWordInOperand(0);
-  const ir::Instruction* tInst = get_def_use_mgr()->GetDef(tId);
-  if (dead_insts_.find(tInst) != dead_insts_.end()) {
-    return true;
+  ir::Instruction* tInst = get_def_use_mgr()->GetDef(tId);
+  if (ir::IsAnnotationInst(tInst->opcode())) {
+    // This must be a decoration group. We go through annotations in a specific
+    // order. So if this is not used by any group or group member decorates, it
+    // is dead.
+    assert(tInst->opcode() == SpvOpDecorationGroup);
+    bool dead = true;
+    get_def_use_mgr()->ForEachUser(tInst, [&dead](ir::Instruction* user) {
+      if (user->opcode() == SpvOpGroupDecorate ||
+          user->opcode() == SpvOpGroupMemberDecorate)
+        dead = false;
+    });
+    return dead;
   }
-  return false;
+  return IsDead(tInst);
 }
 
 void AggressiveDCEPass::ProcessLoad(uint32_t varId) {
@@ -106,6 +177,7 @@ bool AggressiveDCEPass::IsStructuredIfOrLoopHeader(ir::BasicBlock* bp,
                                                    ir::Instruction** mergeInst,
                                                    ir::Instruction** branchInst,
                                                    uint32_t* mergeBlockId) {
+  if (!bp) return false;
   ir::Instruction* mi = bp->GetMergeInst();
   if (mi == nullptr) return false;
   ir::Instruction* bri = &*bp->tail();
@@ -158,13 +230,6 @@ void AggressiveDCEPass::ComputeBlock2HeaderMaps(
   }
 }
 
-void AggressiveDCEPass::ComputeInst2BlockMap(ir::Function* func) {
-  for (auto& blk : *func) {
-    blk.ForEachInst(
-        [&blk, this](ir::Instruction* ip) { inst2block_[ip] = &blk; });
-  }
-}
-
 void AggressiveDCEPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) {
   std::unique_ptr<ir::Instruction> newBranch(new ir::Instruction(
       context(), SpvOpBranch, 0, 0,
@@ -185,7 +250,7 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist(
         if (op != SpvOpBranchConditional && op != SpvOpBranch) return;
         ir::Instruction* branchInst = user;
         while (true) {
-          ir::BasicBlock* blk = inst2block_[branchInst];
+          ir::BasicBlock* blk = context()->get_instr_block(branchInst);
           ir::Instruction* hdrBranch = block2headerBranch_[blk];
           if (hdrBranch == nullptr) return;
           ir::Instruction* hdrMerge = branch2merge_[hdrBranch];
@@ -218,7 +283,7 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist(
     } else if (op == SpvOpBranch) {
       // An unconditional branch can only be a continue if it is not
       // branching to its own merge block.
-      ir::BasicBlock* blk = inst2block_[user];
+      ir::BasicBlock* blk = context()->get_instr_block(user);
       ir::Instruction* hdrBranch = block2headerBranch_[blk];
       if (hdrBranch == nullptr) return;
       ir::Instruction* hdrMerge = branch2merge_[hdrBranch];
@@ -234,8 +299,14 @@ void AggressiveDCEPass::AddBreaksAndContinuesToWorklist(
 }
 
 bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
-  // Compute map from instruction to block
-  ComputeInst2BlockMap(func);
+  // Mark function parameters as live.
+  AddToWorklist(&func->DefInst());
+  func->ForEachParam(
+      [this](const ir::Instruction* param) {
+        AddToWorklist(const_cast<ir::Instruction*>(param));
+      },
+      false);
+
   // Compute map from block to controlling conditional branch
   std::list<ir::BasicBlock*> structuredOrder;
   cfg()->ComputeStructuredOrder(func, &*func->begin(), &structuredOrder);
@@ -320,12 +391,6 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
   // If privates are not like local, add their stores to worklist
   if (!private_like_local_)
     for (auto& ps : private_stores_) AddToWorklist(ps);
-  // Add OpGroupDecorates to worklist because they are a pain to remove
-  // ids from.
-  // TODO(greg-lunarg): Handle dead ids in OpGroupDecorate
-  for (auto& ai : get_module()->annotations()) {
-    if (ai.opcode() == SpvOpGroupDecorate) AddToWorklist(&ai);
-  }
   // Perform closure on live instruction set.
   while (!worklist_.empty()) {
     ir::Instruction* liveInst = worklist_.front();
@@ -338,11 +403,14 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
       if (inInst->opcode() == SpvOpLabel && liveInst->IsBranch()) return;
       if (!IsLive(inInst)) AddToWorklist(inInst);
     });
+    if (liveInst->type_id() != 0) {
+      AddToWorklist(get_def_use_mgr()->GetDef(liveInst->type_id()));
+    }
     // If in a structured if or loop construct, add the controlling
     // conditional branch and its merge. Any containing control construct
     // is marked live when the merge and branch are processed out of the
     // worklist.
-    ir::BasicBlock* blk = inst2block_[liveInst];
+    ir::BasicBlock* blk = context()->get_instr_block(liveInst);
     ir::Instruction* branchInst = block2headerBranch_[blk];
     if (branchInst != nullptr && !IsLive(branchInst)) {
       AddToWorklist(branchInst);
@@ -376,67 +444,19 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
     }
     worklist_.pop();
   }
-  // Mark all non-live instructions dead except non-structured branches, which
-  // now should be considered live unless their block is deleted.
-  for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) {
-    for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) {
-      if (IsLive(&*ii)) continue;
-      // TODO(greg-lunarg
-      // https://github.com/KhronosGroup/SPIRV-Tools/issues/1021) This should be
-      // using ii->IsBranch(), but this code does not handle OpSwitch
-      // instructions yet.
-      if ((ii->opcode() == SpvOpBranch ||
-           ii->opcode() == SpvOpBranchConditional) &&
-          !IsStructuredIfOrLoopHeader(*bi, nullptr, nullptr, nullptr))
-        continue;
-      dead_insts_.insert(&*ii);
-    }
-  }
-  // Remove debug and annotation statements referencing dead instructions.
-  // This must be done before killing the instructions, otherwise there are
-  // dead objects in the def/use database.
-  ir::Instruction* instruction = &*get_module()->debug2_begin();
-  while (instruction) {
-    if (instruction->opcode() != SpvOpName) {
-      instruction = instruction->NextNode();
-      continue;
-    }
-
-    if (IsTargetDead(instruction)) {
-      instruction = context()->KillInst(instruction);
-      modified = true;
-    } else {
-      instruction = instruction->NextNode();
-    }
-  }
-
-  instruction = &*get_module()->annotation_begin();
-  while (instruction) {
-    if (instruction->opcode() != SpvOpDecorate &&
-        instruction->opcode() != SpvOpDecorateId) {
-      instruction = instruction->NextNode();
-      continue;
-    }
-
-    if (IsTargetDead(instruction)) {
-      instruction = context()->KillInst(instruction);
-      modified = true;
-    } else {
-      instruction = instruction->NextNode();
-    }
-  }
 
   // Kill dead instructions and remember dead blocks
   for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();) {
     uint32_t mergeBlockId = 0;
     (*bi)->ForEachInst([this, &modified, &mergeBlockId](ir::Instruction* inst) {
-      if (dead_insts_.find(inst) == dead_insts_.end()) return;
+      if (!IsDead(inst)) return;
+      if (inst->opcode() == SpvOpLabel) return;
       // If dead instruction is selection merge, remember merge block
       // for new branch at end of block
       if (inst->opcode() == SpvOpSelectionMerge ||
           inst->opcode() == SpvOpLoopMerge)
         mergeBlockId = inst->GetSingleWordInOperand(0);
-      context()->KillInst(inst);
+      to_kill_.push_back(inst);
       modified = true;
     });
     // If a structured if or loop was deleted, add a branch to its merge
@@ -450,8 +470,6 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) {
       ++bi;
     }
   }
-  // Cleanup all CFG including all unreachable blocks
-  CFGCleanup(func);
 
   return modified;
 }
@@ -463,12 +481,20 @@ void AggressiveDCEPass::Initialize(ir::IRContext* c) {
   worklist_ = std::queue<ir::Instruction*>{};
   live_insts_.clear();
   live_local_vars_.clear();
-  dead_insts_.clear();
 
   // Initialize extensions whitelist
   InitExtensions();
 }
 
+void AggressiveDCEPass::InitializeModuleScopeLiveInstructions() {
+  for (auto& exec : get_module()->execution_modes()) {
+    AddToWorklist(&exec);
+  }
+  for (auto& entry : get_module()->entry_points()) {
+    AddToWorklist(&entry);
+  }
+}
+
 Pass::Status AggressiveDCEPass::ProcessImpl() {
   // Current functionality assumes shader capability
   // TODO(greg-lunarg): Handle additional capabilities
@@ -482,12 +508,158 @@ Pass::Status AggressiveDCEPass::ProcessImpl() {
   // If any extensions in the module are not explicitly supported,
   // return unmodified.
   if (!AllExtensionsSupported()) return Status::SuccessWithoutChange;
-  // Process all entry point functions
+
+  // Eliminate Dead functions.
+  bool modified = EliminateDeadFunctions();
+
+  InitializeModuleScopeLiveInstructions();
+
+  // Process all entry point functions.
   ProcessFunction pfn = [this](ir::Function* fp) { return AggressiveDCE(fp); };
-  bool modified = ProcessEntryPointCallTree(pfn, get_module());
+  modified |= ProcessEntryPointCallTree(pfn, get_module());
+
+  // Process module-level instructions. Now that all live instructions have
+  // been marked, it is safe to remove dead global values.
+  modified |= ProcessGlobalValues();
+
+  // Kill all dead instructions.
+  for (auto inst : to_kill_) {
+    context()->KillInst(inst);
+  }
+
+  // Cleanup all CFG including all unreachable blocks.
+  ProcessFunction cleanup = [this](ir::Function* f) { return CFGCleanup(f); };
+  modified |= ProcessEntryPointCallTree(cleanup, get_module());
+
   return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
 }
 
+bool AggressiveDCEPass::EliminateDeadFunctions() {
+  // Identify live functions first. Those that are not live
+  // are dead. ADCE is disabled for non-shaders so we do not check for exported
+  // functions here.
+  std::unordered_set<const ir::Function*> live_function_set;
+  ProcessFunction mark_live = [&live_function_set](ir::Function* fp) {
+    live_function_set.insert(fp);
+    return false;
+  };
+  ProcessEntryPointCallTree(mark_live, get_module());
+
+  bool modified = false;
+  for (auto funcIter = get_module()->begin();
+       funcIter != get_module()->end();) {
+    if (live_function_set.count(&*funcIter) == 0) {
+      modified = true;
+      EliminateFunction(&*funcIter);
+      funcIter = funcIter.Erase();
+    } else {
+      ++funcIter;
+    }
+  }
+
+  return modified;
+}
+
+void AggressiveDCEPass::EliminateFunction(ir::Function* func) {
+  // Remove all of the instruction in the function body
+  func->ForEachInst(
+      [this](ir::Instruction* inst) { context()->KillInst(inst); }, true);
+}
+
+bool AggressiveDCEPass::ProcessGlobalValues() {
+  // Remove debug and annotation statements referencing dead instructions.
+  // This must be done before killing the instructions, otherwise there are
+  // dead objects in the def/use database.
+  bool modified = false;
+  ir::Instruction* instruction = &*get_module()->debug2_begin();
+  while (instruction) {
+    if (instruction->opcode() != SpvOpName) {
+      instruction = instruction->NextNode();
+      continue;
+    }
+
+    if (IsTargetDead(instruction)) {
+      instruction = context()->KillInst(instruction);
+      modified = true;
+    } else {
+      instruction = instruction->NextNode();
+    }
+  }
+
+  // This code removes all unnecessary decorations safely (see #1174). It also
+  // does so in a more efficient manner than deleting them only as the targets
+  // are deleted.
+  std::vector<ir::Instruction*> annotations;
+  for (auto& inst : get_module()->annotations()) annotations.push_back(&inst);
+  std::sort(annotations.begin(), annotations.end(), DecorationLess());
+  for (auto annotation : annotations) {
+    switch (annotation->opcode()) {
+      case SpvOpDecorate:
+      case SpvOpMemberDecorate:
+      case SpvOpDecorateId:
+        if (IsTargetDead(annotation)) context()->KillInst(annotation);
+        break;
+      case SpvOpGroupDecorate: {
+        // Go through the targets of this group decorate. Remove each dead
+        // target. If all targets are dead, remove this decoration.
+        bool dead = true;
+        for (uint32_t i = 1; i < annotation->NumOperands();) {
+          ir::Instruction* opInst =
+              get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
+          if (IsDead(opInst)) {
+            // Don't increment |i|.
+            annotation->RemoveOperand(i);
+          } else {
+            i++;
+            dead = false;
+          }
+        }
+        if (dead) context()->KillInst(annotation);
+        break;
+      }
+      case SpvOpGroupMemberDecorate: {
+        // Go through the targets of this group member decorate. Remove each
+        // dead target (and member index). If all targets are dead, remove this
+        // decoration.
+        bool dead = true;
+        for (uint32_t i = 1; i < annotation->NumOperands();) {
+          ir::Instruction* opInst =
+              get_def_use_mgr()->GetDef(annotation->GetSingleWordOperand(i));
+          if (IsDead(opInst)) {
+            // Don't increment |i|.
+            annotation->RemoveOperand(i + 1);
+            annotation->RemoveOperand(i);
+          } else {
+            i += 2;
+            dead = false;
+          }
+        }
+        if (dead) context()->KillInst(annotation);
+        break;
+      }
+      case SpvOpDecorationGroup:
+        // By the time we hit decoration groups we've checked everything that
+        // can target them. So if they have no uses they must be dead.
+        if (get_def_use_mgr()->NumUsers(annotation) == 0)
+          context()->KillInst(annotation);
+        break;
+      default:
+        assert(false);
+        break;
+    }
+  }
+
+  // Since ADCE is disabled for non-shaders, we don't check for export linkage
+  // attributes here.
+  for (auto& val : get_module()->types_values()) {
+    if (IsDead(&val)) {
+      to_kill_.push_back(&val);
+    }
+  }
+
+  return modified;
+}
+
 AggressiveDCEPass::AggressiveDCEPass() {}
 
 Pass::Status AggressiveDCEPass::Process(ir::IRContext* c) {
index 7526d8a..4c49986 100644 (file)
@@ -55,18 +55,25 @@ class AggressiveDCEPass : public MemPass {
 
   // Return true if |varId| is variable of function storage class or is
   // private variable and privates can be optimized like locals (see
-  // privates_like_local_)
+  // privates_like_local_).
   bool IsLocalVar(uint32_t varId);
 
-  // Return true if |inst| is marked live
-  bool IsLive(ir::Instruction* inst) {
+  // Return true if |inst| is marked live.
+  bool IsLive(const ir::Instruction* inst) const {
     return live_insts_.find(inst) != live_insts_.end();
   }
 
+  // Returns true if |inst| is dead.
+  bool IsDead(ir::Instruction* inst);
+
+  // Adds entry points and execution modes to the worklist for processing with
+  // the first function.
+  void InitializeModuleScopeLiveInstructions();
+
   // Add |inst| to worklist_ and live_insts_.
   void AddToWorklist(ir::Instruction* inst) {
-    worklist_.push(inst);
     live_insts_.insert(inst);
+    worklist_.push(inst);
   }
 
   // Add all store instruction which use |ptrId|, directly or indirectly,
@@ -79,8 +86,9 @@ class AggressiveDCEPass : public MemPass {
   // Return true if all extensions in this module are supported by this pass.
   bool AllExtensionsSupported() const;
 
-  // Returns true if |inst| is dead.  An instruction is dead if its result id
-  // is used in decoration or debug instructions only.
+  // Returns true if the target of |inst| is dead.  An instruction is dead if
+  // its result id is used in decoration or debug instructions only. |inst| is
+  // assumed to be OpName, OpMemberName or an annotation instruction.
   bool IsTargetDead(ir::Instruction* inst);
 
   // If |varId| is local, mark all stores of varId as live.
@@ -89,6 +97,8 @@ class AggressiveDCEPass : public MemPass {
   // If |bp| is structured if or loop header block, return true and set
   // |mergeInst| to the merge instruction, |branchInst| to the conditional
   // branch and |mergeBlockId| to the merge block if they are not nullptr.
+  // Any of |mergeInst|, |branchInst| or |mergeBlockId| may be a null pointer.
+  // Returns false if |bp| is a null pointer.
   bool IsStructuredIfOrLoopHeader(ir::BasicBlock* bp,
                                   ir::Instruction** mergeInst,
                                   ir::Instruction** branchInst,
@@ -98,9 +108,6 @@ class AggressiveDCEPass : public MemPass {
   // to order blocks.
   void ComputeBlock2HeaderMaps(std::list<ir::BasicBlock*>& structuredOrder);
 
-  // Initialize inst2block_ for |func|.
-  void ComputeInst2BlockMap(ir::Function* func);
-
   // Add branch to |labelId| to end of block |bp|.
   void AddBranch(uint32_t labelId, ir::BasicBlock* bp);
 
@@ -108,10 +115,20 @@ class AggressiveDCEPass : public MemPass {
   // |mergeInst| to worklist if not already live
   void AddBreaksAndContinuesToWorklist(ir::Instruction* mergeInst);
 
+  // Eliminates dead debug2 and annotation instructions. Marks dead globals for
+  // removal (e.g. types, constants and variables).
+  bool ProcessGlobalValues();
+
+  // Erases functions that are unreachable from the entry points of the module.
+  bool EliminateDeadFunctions();
+
+  // Removes |func| from the module and deletes all its instructions.
+  void EliminateFunction(ir::Function* func);
+
   // For function |func|, mark all Stores to non-function-scope variables
   // and block terminating instructions as live. Recursively mark the values
-  // they use. When complete, delete any non-live instructions. Return true
-  // if the function has been modified.
+  // they use. When complete, mark any non-live instructions to be deleted.
+  // Returns true if the function has been modified.
   //
   // Note: This function does not delete useless control structures. All
   // existing control structures will remain. This can leave not-insignificant
@@ -147,17 +164,6 @@ class AggressiveDCEPass : public MemPass {
   // Map from branch to its associated merge instruction, if any
   std::unordered_map<ir::Instruction*, ir::Instruction*> branch2merge_;
 
-  // Map from instruction containing block
-  std::unordered_map<ir::Instruction*, ir::BasicBlock*> inst2block_;
-
-  // Map from block's label id to block.
-  std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
-
-  // Map from block to its structured successor blocks. See
-  // ComputeStructuredSuccessors() for definition.
-  std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
-      block2structured_succs_;
-
   // Store instructions to variables of private storage
   std::vector<ir::Instruction*> private_stores_;
 
@@ -167,8 +173,9 @@ class AggressiveDCEPass : public MemPass {
   // Live Local Variables
   std::unordered_set<uint32_t> live_local_vars_;
 
-  // Dead instructions. Use for debug cleanup.
-  std::unordered_set<const ir::Instruction*> dead_insts_;
+  // List of instructions to delete. Deletion is delayed until debug and
+  // annotation instructions are processed.
+  std::vector<ir::Instruction*> to_kill_;
 
   // Extensions supported by this pass.
   std::unordered_set<std::string> extensions_whitelist_;
index 6c8b854..1f60356 100644 (file)
@@ -118,7 +118,7 @@ Optimizer& Optimizer::RegisterPerformancePasses() {
   return RegisterPass(CreateRemoveDuplicatesPass())
       .RegisterPass(CreateMergeReturnPass())
       .RegisterPass(CreateInlineExhaustivePass())
-      .RegisterPass(CreateEliminateDeadFunctionsPass())
+      .RegisterPass(CreateAggressiveDCEPass())
       .RegisterPass(CreateScalarReplacementPass())
       .RegisterPass(CreateLocalAccessChainConvertPass())
       .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
@@ -134,14 +134,14 @@ Optimizer& Optimizer::RegisterPerformancePasses() {
       .RegisterPass(CreateCFGCleanupPass())
       // Currently exposing driver bugs resulting in crashes (#946)
       // .RegisterPass(CreateCommonUniformElimPass())
-      .RegisterPass(CreateDeadVariableEliminationPass());
+      .RegisterPass(CreateAggressiveDCEPass());
 }
 
 Optimizer& Optimizer::RegisterSizePasses() {
   return RegisterPass(CreateRemoveDuplicatesPass())
       .RegisterPass(CreateMergeReturnPass())
       .RegisterPass(CreateInlineExhaustivePass())
-      .RegisterPass(CreateEliminateDeadFunctionsPass())
+      .RegisterPass(CreateAggressiveDCEPass())
       .RegisterPass(CreateLocalAccessChainConvertPass())
       .RegisterPass(CreateLocalSingleBlockLoadStoreElimPass())
       .RegisterPass(CreateLocalSingleStoreElimPass())
@@ -156,7 +156,7 @@ Optimizer& Optimizer::RegisterSizePasses() {
       .RegisterPass(CreateCFGCleanupPass())
       // Currently exposing driver bugs resulting in crashes (#946)
       // .RegisterPass(CreateCommonUniformElimPass())
-      .RegisterPass(CreateDeadVariableEliminationPass());
+      .RegisterPass(CreateAggressiveDCEPass());
 }
 
 bool Optimizer::Run(const uint32_t* original_binary,
index 44df87a..8099a0a 100644 (file)
@@ -13,6 +13,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+#include "assembly_builder.h"
 #include "pass_fixture.h"
 #include "pass_utils.h"
 
@@ -150,11 +151,10 @@ OpName %v "v"
 OpName %BaseColor "BaseColor"
 OpName %Dead "Dead"
 OpName %iv2 "iv2"
-OpName %ResType "ResType"
 OpName %Color "Color"
 )";
 
-  const std::string predefs2 =
+  const std::string predefs2_before =
       R"(%void = OpTypeVoid
 %11 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -172,6 +172,23 @@ OpName %Color "Color"
 %Color = OpVariable %_ptr_Output_v4float Output
 )";
 
+  const std::string predefs2_after =
+      R"(%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%Dead = OpVariable %_ptr_Input_v4float Input
+%int = OpTypeInt 32 1
+%v4int = OpTypeVector %int 4
+%_ptr_Output_v4int = OpTypePointer Output %v4int
+%iv2 = OpVariable %_ptr_Output_v4int Output
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%Color = OpVariable %_ptr_Output_v4float Output
+)";
+
   const std::string func_before =
       R"(%main = OpFunction %void None %11
 %20 = OpLabel
@@ -203,8 +220,8 @@ OpFunctionEnd
 )";
 
   SinglePassRunAndCheck<opt::AggressiveDCEPass>(
-      predefs1 + names_before + predefs2 + func_before,
-      predefs1 + names_after + predefs2 + func_after, true, true);
+      predefs1 + names_before + predefs2_before + func_before,
+      predefs1 + names_after + predefs2_after + func_after, true, true);
 }
 
 TEST_F(AggressiveDCETest, EliminateDecorate) {
@@ -249,7 +266,7 @@ OpName %Dead "Dead"
 OpName %gl_FragColor "gl_FragColor"
 )";
 
-  const std::string predefs2 =
+  const std::string predefs2_before =
       R"(%void = OpTypeVoid
 %10 = OpTypeFunction %void
 %float = OpTypeFloat 32
@@ -263,6 +280,19 @@ OpName %gl_FragColor "gl_FragColor"
 %gl_FragColor = OpVariable %_ptr_Output_v4float Output
 )";
 
+  const std::string predefs2_after =
+      R"(%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%gl_FragColor = OpVariable %_ptr_Output_v4float Output
+)";
+
   const std::string func_before =
       R"(%main = OpFunction %void None %10
 %17 = OpLabel
@@ -292,8 +322,8 @@ OpFunctionEnd
 )";
 
   SinglePassRunAndCheck<opt::AggressiveDCEPass>(
-      predefs1 + names_before + predefs2 + func_before,
-      predefs1 + names_after + predefs2 + func_after, true, true);
+      predefs1 + names_before + predefs2_before + func_before,
+      predefs1 + names_after + predefs2_after + func_after, true, true);
 }
 
 TEST_F(AggressiveDCETest, Simple) {
@@ -828,10 +858,6 @@ OpEntryPoint Fragment %main "main" %outColor %texCoords
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 140
 OpName %main "main"
-OpName %S_t "S_t"
-OpMemberName %S_t 0 "v0"
-OpMemberName %S_t 1 "v1"
-OpMemberName %S_t 2 "smp"
 OpName %outColor "outColor"
 OpName %sampler15 "sampler15"
 OpName %texCoords "texCoords"
@@ -845,16 +871,8 @@ OpDecorate %sampler15 DescriptorSet 0
 %outColor = OpVariable %_ptr_Output_v4float Output
 %14 = OpTypeImage %float 2D 0 0 0 1 Unknown
 %15 = OpTypeSampledImage %14
-%S_t = OpTypeStruct %v2float %v2float %15
-%_ptr_Function_S_t = OpTypePointer Function %S_t
-%17 = OpTypeFunction %void %_ptr_Function_S_t
 %_ptr_UniformConstant_15 = OpTypePointer UniformConstant %15
-%_ptr_Function_15 = OpTypePointer Function %15
 %sampler15 = OpVariable %_ptr_UniformConstant_15 UniformConstant
-%int = OpTypeInt 32 1
-%int_0 = OpConstant %int 0
-%int_2 = OpConstant %int 2
-%_ptr_Function_v2float = OpTypePointer Function %v2float
 %_ptr_Input_v2float = OpTypePointer Input %v2float
 %texCoords = OpVariable %_ptr_Input_v2float Input
 )";
@@ -977,7 +995,7 @@ TEST_F(AggressiveDCETest, PrivateStoreElimInEntryNoCalls) {
   //     OutColor = v;
   // }
 
-  const std::string predefs =
+  const std::string predefs_before =
       R"(OpCapability Shader
 %1 = OpExtInstImport "GLSL.std.450"
 OpMemoryModel Logical GLSL450
@@ -1007,6 +1025,33 @@ OpDecorate %OutColor Location 0
 %OutColor = OpVariable %_ptr_Output_v4float Output
 )";
 
+  const std::string predefs_after =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %BaseColor %Dead %OutColor
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 450
+OpName %main "main"
+OpName %v "v"
+OpName %BaseColor "BaseColor"
+OpName %Dead "Dead"
+OpName %OutColor "OutColor"
+OpDecorate %BaseColor Location 0
+OpDecorate %Dead Location 1
+OpDecorate %OutColor Location 0
+%void = OpTypeVoid
+%9 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+%BaseColor = OpVariable %_ptr_Input_v4float Input
+%Dead = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+%OutColor = OpVariable %_ptr_Output_v4float Output
+)";
+
   const std::string main_before =
       R"(%main = OpFunction %void None %9
 %16 = OpLabel
@@ -1036,7 +1081,7 @@ OpFunctionEnd
 )";
 
   SinglePassRunAndCheck<opt::AggressiveDCEPass>(
-      predefs + main_before, predefs + main_after, true, true);
+      predefs_before + main_before, predefs_after + main_after, true, true);
 }
 
 TEST_F(AggressiveDCETest, NoPrivateStoreElimIfLoad) {
@@ -1290,14 +1335,6 @@ OpDecorate %OutColor Location 0
 %v4float = OpTypeVector %float 4
 %_ptr_Input_v4float = OpTypePointer Input %v4float
 %BaseColor = OpVariable %_ptr_Input_v4float Input
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%_ptr_Input_float = OpTypePointer Input %float
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%_ptr_Function_float = OpTypePointer Function %float
-%uint_1 = OpConstant %uint 1
-%uint_2 = OpConstant %uint 2
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %OutColor = OpVariable %_ptr_Output_v4float Output
 %float_1 = OpConstant %float 1
@@ -1407,13 +1444,6 @@ OpDecorate %OutColor Location 0
 %v4float = OpTypeVector %float 4
 %_ptr_Input_v4float = OpTypePointer Input %v4float
 %BaseColor = OpVariable %_ptr_Input_v4float Input
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%_ptr_Input_float = OpTypePointer Input %float
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%_ptr_Function_float = OpTypePointer Function %float
-%uint_1 = OpConstant %uint 1
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %OutColor = OpVariable %_ptr_Output_v4float Output
 %float_1 = OpConstant %float 1
@@ -1640,16 +1670,6 @@ OpDecorate %OutColor Location 0
 %v4float = OpTypeVector %float 4
 %_ptr_Input_v4float = OpTypePointer Input %v4float
 %BaseColor = OpVariable %_ptr_Input_v4float Input
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%_ptr_Input_float = OpTypePointer Input %float
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%uint_1 = OpConstant %uint 1
-%_ptr_Function_float = OpTypePointer Function %float
-%float_0_25 = OpConstant %float 0.25
-%float_0_5 = OpConstant %float 0.5
-%float_0_75 = OpConstant %float 0.75
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %OutColor = OpVariable %_ptr_Output_v4float Output
 %float_1 = OpConstant %float 1
@@ -1928,25 +1948,24 @@ OpDecorate %OutColor Location 0
 %_ptr_Input_float = OpTypePointer Input %float
 %float_0 = OpConstant %float 0
 %bool = OpTypeBool
-%_ptr_Function_float = OpTypePointer Function %float
 %float_1 = OpConstant %float 1
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %OutColor = OpVariable %_ptr_Output_v4float Output
 %main = OpFunction %void None %6
-%18 = OpLabel
-%19 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
-%20 = OpLoad %float %19
-%21 = OpFOrdEqual %bool %20 %float_0
-OpSelectionMerge %22 None
-OpBranchConditional %21 %23 %24
-%23 = OpLabel
-OpBranch %22
-%24 = OpLabel
-OpBranch %22
+%17 = OpLabel
+%18 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0
+%19 = OpLoad %float %18
+%20 = OpFOrdEqual %bool %19 %float_0
+OpSelectionMerge %21 None
+OpBranchConditional %20 %22 %23
 %22 = OpLabel
-%25 = OpPhi %float %float_0 %23 %float_1 %24
-%26 = OpCompositeConstruct %v4float %25 %25 %25 %25
-OpStore %OutColor %26
+OpBranch %21
+%23 = OpLabel
+OpBranch %21
+%21 = OpLabel
+%24 = OpPhi %float %float_0 %22 %float_1 %23
+%25 = OpCompositeConstruct %v4float %24 %24 %24 %24
+OpStore %OutColor %25
 OpReturn
 OpFunctionEnd
 )";
@@ -2085,7 +2104,6 @@ OpDecorate %o Location 0
 %int_0 = OpConstant %int 0
 %int_10 = OpConstant %int 10
 %bool = OpTypeBool
-%int_2 = OpConstant %int 2
 %uint = OpTypeInt 32 0
 %uint_10 = OpConstant %uint 10
 %_arr_float_uint_10 = OpTypeArray %float %uint_10
@@ -2097,36 +2115,36 @@ OpDecorate %o Location 0
 %_ptr_Output_float = OpTypePointer Output %float
 %o = OpVariable %_ptr_Output_float Output
 %main = OpFunction %void None %10
-%26 = OpLabel
+%25 = OpLabel
 %s = OpVariable %_ptr_Function_float Function
 %i = OpVariable %_ptr_Function_int Function
 OpStore %s %float_0
 OpStore %i %int_0
-OpBranch %27
-%27 = OpLabel
-OpLoopMerge %28 %29 None
-OpBranch %30
-%30 = OpLabel
-%31 = OpLoad %int %i
-%32 = OpSLessThan %bool %31 %int_10
-OpSelectionMerge %33 None
-OpBranchConditional %32 %33 %28
-%33 = OpLabel
-%34 = OpLoad %int %i
-%35 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %34
-%36 = OpLoad %float %35
-%37 = OpLoad %float %s
-%38 = OpFAdd %float %37 %36
-OpStore %s %38
+OpBranch %26
+%26 = OpLabel
+OpLoopMerge %27 %28 None
 OpBranch %29
 %29 = OpLabel
-%39 = OpLoad %int %i
-%40 = OpIAdd %int %39 %int_1
-OpStore %i %40
-OpBranch %27
+%30 = OpLoad %int %i
+%31 = OpSLessThan %bool %30 %int_10
+OpSelectionMerge %32 None
+OpBranchConditional %31 %32 %27
+%32 = OpLabel
+%33 = OpLoad %int %i
+%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %33
+%35 = OpLoad %float %34
+%36 = OpLoad %float %s
+%37 = OpFAdd %float %36 %35
+OpStore %s %37
+OpBranch %28
 %28 = OpLabel
-%41 = OpLoad %float %s
-OpStore %o %41
+%38 = OpLoad %int %i
+%39 = OpIAdd %int %38 %int_1
+OpStore %i %39
+OpBranch %26
+%27 = OpLabel
+%40 = OpLoad %float %s
+OpStore %o %40
 OpReturn
 OpFunctionEnd
 )";
@@ -2180,13 +2198,10 @@ OpName %gl_FragColor "gl_FragColor"
       R"(OpName %main "main"
 OpName %v "v"
 OpName %BaseColor "BaseColor"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_I"
-OpName %_ ""
 OpName %gl_FragColor "gl_FragColor"
 )";
 
-  const std::string predefs2 =
+  const std::string predefs2_before =
       R"(OpMemberDecorate %U_t 0 Offset 0
 OpDecorate %U_t Block
 OpDecorate %_ DescriptorSet 0
@@ -2213,6 +2228,18 @@ OpDecorate %_ DescriptorSet 0
 %gl_FragColor = OpVariable %_ptr_Output_v4float Output
 )";
 
+  const std::string predefs2_after =
+      R"(%void = OpTypeVoid
+%11 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%v4float = OpTypeVector %float 4
+%_ptr_Function_v4float = OpTypePointer Function %v4float
+%_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
+)";
+
   const std::string func_before =
       R"(%main = OpFunction %void None %11
 %27 = OpLabel
@@ -2267,8 +2294,8 @@ OpFunctionEnd
 )";
 
   SinglePassRunAndCheck<opt::AggressiveDCEPass>(
-      predefs1 + names_before + predefs2 + func_before,
-      predefs1 + names_after + predefs2 + func_after, true, true);
+      predefs1 + names_before + predefs2_before + func_before,
+      predefs1 + names_after + predefs2_after + func_after, true, true);
 }
 
 TEST_F(AggressiveDCETest, NoEliminateBusyLoop) {
@@ -2389,10 +2416,8 @@ OpDecorate %o Location 0
 %void = OpTypeVoid
 %8 = OpTypeFunction %void
 %float = OpTypeFloat 32
-%_ptr_Function_float = OpTypePointer Function %float
 %float_0 = OpConstant %float 0
 %int = OpTypeInt 32 1
-%_ptr_Function_int = OpTypePointer Function %int
 %int_0 = OpConstant %int 0
 %int_10 = OpConstant %int 10
 %bool = OpTypeBool
@@ -2407,26 +2432,26 @@ OpDecorate %o Location 0
 %_ptr_Output_float = OpTypePointer Output %float
 %o = OpVariable %_ptr_Output_float Output
 %main = OpFunction %void None %8
-%23 = OpLabel
-OpBranch %24
-%24 = OpLabel
-%25 = OpPhi %float %float_0 %23 %26 %27
-%28 = OpPhi %int %int_0 %23 %29 %27
-OpLoopMerge %30 %27 None
-OpBranch %31
+%21 = OpLabel
+OpBranch %22
+%22 = OpLabel
+%23 = OpPhi %float %float_0 %21 %24 %25
+%26 = OpPhi %int %int_0 %21 %27 %25
+OpLoopMerge %28 %25 None
+OpBranch %29
+%29 = OpLabel
+%30 = OpSLessThan %bool %26 %int_10
+OpBranchConditional %30 %31 %28
 %31 = OpLabel
-%32 = OpSLessThan %bool %28 %int_10
-OpBranchConditional %32 %33 %30
-%33 = OpLabel
-%34 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %28
-%35 = OpLoad %float %34
-%26 = OpFAdd %float %25 %35
-OpBranch %27
-%27 = OpLabel
-%29 = OpIAdd %int %28 %int_1
-OpBranch %24
-%30 = OpLabel
-OpStore %o %25
+%32 = OpAccessChain %_ptr_Uniform_float %_ %int_0 %26
+%33 = OpLoad %float %32
+%24 = OpFAdd %float %23 %33
+OpBranch %25
+%25 = OpLabel
+%27 = OpIAdd %int %26 %int_1
+OpBranch %22
+%28 = OpLabel
+OpStore %o %23
 OpReturn
 OpFunctionEnd
 )";
@@ -2498,14 +2523,6 @@ OpDecorate %OutColor Location 0
 %v4float = OpTypeVector %float 4
 %_ptr_Input_v4float = OpTypePointer Input %v4float
 %BaseColor = OpVariable %_ptr_Input_v4float Input
-%uint = OpTypeInt 32 0
-%uint_0 = OpConstant %uint 0
-%_ptr_Input_float = OpTypePointer Input %float
-%float_0 = OpConstant %float 0
-%bool = OpTypeBool
-%_ptr_Function_float = OpTypePointer Function %float
-%uint_1 = OpConstant %uint 1
-%uint_2 = OpConstant %uint 2
 %_ptr_Output_v4float = OpTypePointer Output %v4float
 %OutColor = OpVariable %_ptr_Output_v4float Output
 )";
@@ -2822,35 +2839,12 @@ OpEntryPoint Fragment %main "main" %o
 OpExecutionMode %main OriginUpperLeft
 OpSource GLSL 430
 OpName %main "main"
-OpName %U_t "U_t"
-OpMemberName %U_t 0 "g_F"
-OpName %_ ""
 OpName %o "o"
-OpDecorate %_arr_float_uint_10 ArrayStride 4
-OpDecorate %_arr__arr_float_uint_10_uint_10 ArrayStride 40
-OpMemberDecorate %U_t 0 Offset 0
-OpDecorate %U_t BufferBlock
-OpDecorate %_ DescriptorSet 0
 OpDecorate %o Location 0
 %void = OpTypeVoid
 %12 = OpTypeFunction %void
 %float = OpTypeFloat 32
-%_ptr_Function_float = OpTypePointer Function %float
 %float_0 = OpConstant %float 0
-%int = OpTypeInt 32 1
-%_ptr_Function_int = OpTypePointer Function %int
-%int_0 = OpConstant %int 0
-%int_10 = OpConstant %int 10
-%bool = OpTypeBool
-%uint = OpTypeInt 32 0
-%uint_10 = OpConstant %uint 10
-%_arr_float_uint_10 = OpTypeArray %float %uint_10
-%_arr__arr_float_uint_10_uint_10 = OpTypeArray %_arr_float_uint_10 %uint_10
-%U_t = OpTypeStruct %_arr__arr_float_uint_10_uint_10
-%_ptr_Uniform_U_t = OpTypePointer Uniform %U_t
-%_ = OpVariable %_ptr_Uniform_U_t Uniform
-%_ptr_Uniform_float = OpTypePointer Uniform %float
-%int_1 = OpConstant %int 1
 %_ptr_Output_float = OpTypePointer Output %float
 %o = OpVariable %_ptr_Output_float Output
 )";
@@ -2987,7 +2981,6 @@ OpDecorate %3 Location 0
 %float = OpTypeFloat 32
 %float_0 = OpConstant %float 0
 %int = OpTypeInt 32 1
-%_ptr_Function_int = OpTypePointer Function %int
 %int_0 = OpConstant %int 0
 %int_10 = OpConstant %int 10
 %bool = OpTypeBool
@@ -3564,13 +3557,9 @@ OpMemberDecorate %_struct_3 0 Offset 0
 OpDecorate %_runtimearr__struct_3 ArrayStride 16
 OpMemberDecorate %_struct_5 0 Offset 0
 OpDecorate %_struct_5 BufferBlock
-OpMemberDecorate %_struct_6 0 Offset 0
-OpDecorate %_struct_6 BufferBlock
 OpDecorate %2 Location 0
 OpDecorate %7 DescriptorSet 0
 OpDecorate %7 Binding 0
-OpDecorate %8 DescriptorSet 0
-OpDecorate %8 Binding 1
 %void = OpTypeVoid
 %10 = OpTypeFunction %void
 %int = OpTypeInt 32 1
@@ -3583,15 +3572,11 @@ OpDecorate %8 Binding 1
 %_runtimearr__struct_3 = OpTypeRuntimeArray %_struct_3
 %_struct_5 = OpTypeStruct %_runtimearr__struct_3
 %_ptr_Uniform__struct_5 = OpTypePointer Uniform %_struct_5
-%_struct_6 = OpTypeStruct %int
-%_ptr_Uniform__struct_6 = OpTypePointer Uniform %_struct_6
 %_ptr_Function__ptr_Uniform__struct_5 = OpTypePointer Function %_ptr_Uniform__struct_5
-%_ptr_Function__ptr_Uniform__struct_6 = OpTypePointer Function %_ptr_Uniform__struct_6
 %int_0 = OpConstant %int 0
 %uint_0 = OpConstant %uint 0
 %2 = OpVariable %_ptr_Output_v4float Output
 %7 = OpVariable %_ptr_Uniform__struct_5 Uniform
-%8 = OpVariable %_ptr_Uniform__struct_6 Uniform
 %1 = OpFunction %void None %10
 %23 = OpLabel
 %24 = OpVariable %_ptr_Function__ptr_Uniform__struct_5 Function
@@ -3607,6 +3592,1510 @@ OpFunctionEnd
   SinglePassRunAndCheck<opt::AggressiveDCEPass>(before, after, true, true);
 }
 
+// %dead is unused.  Make sure we remove it along with its name.
+TEST_F(AggressiveDCETest, RemoveUnreferenced) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%dead = OpVariable %_ptr_Private_float Private
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+%void = OpTypeVoid
+%5 = OpTypeFunction %void
+%main = OpFunction %void None %5
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(before, after, true, true);
+}
+
+// Delete %dead because it is unreferenced.  Then %initializer becomes
+// unreferenced, so remove it as well.
+TEST_F(AggressiveDCETest, RemoveUnreferencedWithInit1) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %dead "dead"
+OpName %initializer "initializer"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%dead = OpVariable %_ptr_Private_float Private %initializer
+%main = OpFunction %void None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%main = OpFunction %void None %6
+%9 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(before, after, true, true);
+}
+
+// Keep %live because it is used, and its initializer.
+TEST_F(AggressiveDCETest, KeepReferenced) {
+  const std::string before =
+      R"(OpCapability Shader
+OpCapability Linkage
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpSource GLSL 150
+OpName %main "main"
+OpName %live "live"
+OpName %initializer "initializer"
+OpName %output "output"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%float = OpTypeFloat 32
+%_ptr_Private_float = OpTypePointer Private %float
+%initializer = OpVariable %_ptr_Private_float Private
+%live = OpVariable %_ptr_Private_float Private %initializer
+%_ptr_Output_float = OpTypePointer Output %float
+%output = OpVariable %_ptr_Output_float Output
+%main = OpFunction %void None %6
+%9 = OpLabel
+%10 = OpLoad %float %live
+OpStore %output %10
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(before, before, true, true);
+}
+
+// This test that the decoration associated with a variable are removed when the
+// variable is removed.
+TEST_F(AggressiveDCETest, RemoveVariableAndDecorations) {
+  const std::string before =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+OpName %B "B"
+OpMemberName %B 0 "a"
+OpName %Bdat "Bdat"
+OpMemberDecorate %B 0 Offset 0
+OpDecorate %B BufferBlock
+OpDecorate %Bdat DescriptorSet 0
+OpDecorate %Bdat Binding 0
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%B = OpTypeStruct %uint
+%_ptr_Uniform_B = OpTypePointer Uniform %B
+%Bdat = OpVariable %_ptr_Uniform_B Uniform
+%int = OpTypeInt 32 1
+%int_0 = OpConstant %int 0
+%uint_1 = OpConstant %uint 1
+%_ptr_Uniform_uint = OpTypePointer Uniform %uint
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  const std::string after =
+      R"(OpCapability Shader
+%1 = OpExtInstImport "GLSL.std.450"
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpSource GLSL 450
+OpName %main "main"
+%void = OpTypeVoid
+%6 = OpTypeFunction %void
+%main = OpFunction %void None %6
+%13 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(before, after, true, true);
+}
+
+TEST_F(AggressiveDCETest, BasicDeleteDeadFunction) {
+  // The function Dead should be removed because it is never called.
+  const std::vector<const char*> common_code = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Fragment %main \"main\"",
+               "OpName %main \"main\"",
+               "OpName %Live \"Live\"",
+       "%void = OpTypeVoid",
+          "%7 = OpTypeFunction %void",
+       "%main = OpFunction %void None %7",
+         "%15 = OpLabel",
+         "%16 = OpFunctionCall %void %Live",
+         "%17 = OpFunctionCall %void %Live",
+               "OpReturn",
+               "OpFunctionEnd",
+  "%Live = OpFunction %void None %7",
+         "%20 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  const std::vector<const char*> dead_function = {
+      // clang-format off
+      "%Dead = OpFunction %void None %7",
+         "%19 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd",
+      // clang-format on
+  };
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      JoinAllInsts(Concat(common_code, dead_function)),
+      JoinAllInsts(common_code), /* skip_nop = */ true);
+}
+
+TEST_F(AggressiveDCETest, BasicKeepLiveFunction) {
+  // Everything is reachable from an entry point, so no functions should be
+  // deleted.
+  const std::vector<const char*> text = {
+      // clang-format off
+               "OpCapability Shader",
+               "OpMemoryModel Logical GLSL450",
+               "OpEntryPoint Fragment %main \"main\"",
+               "OpName %main \"main\"",
+               "OpName %Live1 \"Live1\"",
+               "OpName %Live2 \"Live2\"",
+       "%void = OpTypeVoid",
+          "%7 = OpTypeFunction %void",
+       "%main = OpFunction %void None %7",
+         "%15 = OpLabel",
+         "%16 = OpFunctionCall %void %Live2",
+         "%17 = OpFunctionCall %void %Live1",
+               "OpReturn",
+               "OpFunctionEnd",
+      "%Live1 = OpFunction %void None %7",
+         "%19 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd",
+      "%Live2 = OpFunction %void None %7",
+         "%20 = OpLabel",
+               "OpReturn",
+               "OpFunctionEnd"
+      // clang-format on
+  };
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  std::string assembly = JoinAllInsts(text);
+  auto result = SinglePassRunAndDisassemble<opt::AggressiveDCEPass>(
+      assembly, /* skip_nop = */ true, /* do_validation = */ false);
+  EXPECT_EQ(opt::Pass::Status::SuccessWithoutChange, std::get<1>(result));
+  EXPECT_EQ(assembly, std::get<0>(result));
+}
+
+TEST_F(AggressiveDCETest, BasicRemoveDecorationsAndNames) {
+  // We want to remove the names and decorations associated with results that
+  // are removed.  This test will check for that.
+  const std::string text = R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpName %main "main"
+               OpName %Dead "Dead"
+               OpName %x "x"
+               OpName %y "y"
+               OpName %z "z"
+               OpDecorate %x RelaxedPrecision
+               OpDecorate %y RelaxedPrecision
+               OpDecorate %z RelaxedPrecision
+               OpDecorate %6 RelaxedPrecision
+               OpDecorate %7 RelaxedPrecision
+               OpDecorate %8 RelaxedPrecision
+       %void = OpTypeVoid
+         %10 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+%_ptr_Function_float = OpTypePointer Function %float
+    %float_1 = OpConstant %float 1
+       %main = OpFunction %void None %10
+         %14 = OpLabel
+               OpReturn
+               OpFunctionEnd
+       %Dead = OpFunction %void None %10
+         %15 = OpLabel
+          %x = OpVariable %_ptr_Function_float Function
+          %y = OpVariable %_ptr_Function_float Function
+          %z = OpVariable %_ptr_Function_float Function
+               OpStore %x %float_1
+               OpStore %y %float_1
+          %6 = OpLoad %float %x
+          %7 = OpLoad %float %y
+          %8 = OpFAdd %float %6 %7
+               OpStore %z %8
+               OpReturn
+               OpFunctionEnd)";
+
+  const std::string expected_output = R"(OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Vertex %main "main"
+OpName %main "main"
+%void = OpTypeVoid
+%10 = OpTypeFunction %void
+%main = OpFunction %void None %10
+%14 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+  SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(text, expected_output,
+                                                /* skip_nop = */ true);
+}
+
+#ifdef SPIRV_EFFCEE
+TEST_F(AggressiveDCETest, BasicAllDeadConstants) {
+  const std::string text = R"(
+  ; CHECK-NOT: OpConstant
+               OpCapability Shader
+               OpCapability Float64
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Vertex %main "main"
+               OpName %main "main"
+       %void = OpTypeVoid
+          %4 = OpTypeFunction %void
+       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
+      %false = OpConstantFalse %bool
+        %int = OpTypeInt 32 1
+          %9 = OpConstant %int 1
+       %uint = OpTypeInt 32 0
+         %11 = OpConstant %uint 2
+      %float = OpTypeFloat 32
+         %13 = OpConstant %float 3.14
+     %double = OpTypeFloat 64
+         %15 = OpConstant %double 3.14159265358979
+       %main = OpFunction %void None %4
+         %16 = OpLabel
+               OpReturn
+               OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<opt::AggressiveDCEPass>(text, true);
+}
+#endif  // SPIRV_EFFCEE
+
+TEST_F(AggressiveDCETest, BasicNoneDeadConstants) {
+  const std::vector<const char*> text = {
+      // clang-format off
+                "OpCapability Shader",
+                "OpCapability Float64",
+           "%1 = OpExtInstImport \"GLSL.std.450\"",
+                "OpMemoryModel Logical GLSL450",
+                "OpEntryPoint Vertex %main \"main\" %btv %bfv %iv %uv %fv %dv",
+                "OpName %main \"main\"",
+                "OpName %btv \"btv\"",
+                "OpName %bfv \"bfv\"",
+                "OpName %iv \"iv\"",
+                "OpName %uv \"uv\"",
+                "OpName %fv \"fv\"",
+                "OpName %dv \"dv\"",
+        "%void = OpTypeVoid",
+          "%10 = OpTypeFunction %void",
+        "%bool = OpTypeBool",
+ "%_ptr_Output_bool = OpTypePointer Output %bool",
+        "%true = OpConstantTrue %bool",
+       "%false = OpConstantFalse %bool",
+         "%int = OpTypeInt 32 1",
+ "%_ptr_Output_int = OpTypePointer Output %int",
+       "%int_1 = OpConstant %int 1",
+        "%uint = OpTypeInt 32 0",
+ "%_ptr_Output_uint = OpTypePointer Output %uint",
+      "%uint_2 = OpConstant %uint 2",
+       "%float = OpTypeFloat 32",
+ "%_ptr_Output_float = OpTypePointer Output %float",
+  "%float_3_14 = OpConstant %float 3.14",
+      "%double = OpTypeFloat 64",
+ "%_ptr_Output_double = OpTypePointer Output %double",
+ "%double_3_14159265358979 = OpConstant %double 3.14159265358979",
+         "%btv = OpVariable %_ptr_Output_bool Output",
+         "%bfv = OpVariable %_ptr_Output_bool Output",
+          "%iv = OpVariable %_ptr_Output_int Output",
+          "%uv = OpVariable %_ptr_Output_uint Output",
+          "%fv = OpVariable %_ptr_Output_float Output",
+          "%dv = OpVariable %_ptr_Output_double Output",
+        "%main = OpFunction %void None %10",
+          "%27 = OpLabel",
+                "OpStore %btv %true",
+                "OpStore %bfv %false",
+                "OpStore %iv %int_1",
+                "OpStore %uv %uint_2",
+                "OpStore %fv %float_3_14",
+                "OpStore %dv %double_3_14159265358979",
+                "OpReturn",
+                "OpFunctionEnd",
+      // clang-format on
+  };
+  // All constants are used, so none of them should be eliminated.
+  SinglePassRunAndCheck<opt::AggressiveDCEPass>(
+      JoinAllInsts(text), JoinAllInsts(text), /* skip_nop = */ true);
+}
+
+struct EliminateDeadConstantTestCase {
+  // Type declarations and constants that should be kept.
+  std::vector<std::string> used_consts;
+  // Instructions that refer to constants, this is added to create uses for
+  // some constants so they won't be treated as dead constants.
+  std::vector<std::string> main_insts;
+  // Dead constants that should be removed.
+  std::vector<std::string> dead_consts;
+  // Expectations
+  std::vector<std::string> checks;
+};
+
+// All types that are potentially required in EliminateDeadConstantTest.
+const std::vector<std::string> CommonTypes = {
+    // clang-format off
+    // scalar types
+    "%bool = OpTypeBool",
+    "%uint = OpTypeInt 32 0",
+    "%int = OpTypeInt 32 1",
+    "%float = OpTypeFloat 32",
+    "%double = OpTypeFloat 64",
+    // vector types
+    "%v2bool = OpTypeVector %bool 2",
+    "%v2uint = OpTypeVector %uint 2",
+    "%v2int = OpTypeVector %int 2",
+    "%v3int = OpTypeVector %int 3",
+    "%v4int = OpTypeVector %int 4",
+    "%v2float = OpTypeVector %float 2",
+    "%v3float = OpTypeVector %float 3",
+    "%v2double = OpTypeVector %double 2",
+    // variable pointer types
+    "%_pf_bool = OpTypePointer Output %bool",
+    "%_pf_uint = OpTypePointer Output %uint",
+    "%_pf_int = OpTypePointer Output %int",
+    "%_pf_float = OpTypePointer Output %float",
+    "%_pf_double = OpTypePointer Output %double",
+    "%_pf_v2int = OpTypePointer Output %v2int",
+    "%_pf_v3int = OpTypePointer Output %v3int",
+    "%_pf_v2float = OpTypePointer Output %v2float",
+    "%_pf_v3float = OpTypePointer Output %v3float",
+    "%_pf_v2double = OpTypePointer Output %v2double",
+    // struct types
+    "%inner_struct = OpTypeStruct %bool %int %float %double",
+    "%outer_struct = OpTypeStruct %inner_struct %int %double",
+    "%flat_struct = OpTypeStruct %bool %int %float %double",
+    // clang-format on
+};
+
+using EliminateDeadConstantTest =
+    PassTest<::testing::TestWithParam<EliminateDeadConstantTestCase>>;
+
+#ifdef SPIRV_EFFCEE
+TEST_P(EliminateDeadConstantTest, Custom) {
+  auto& tc = GetParam();
+  AssemblyBuilder builder;
+  builder.AppendTypesConstantsGlobals(CommonTypes)
+      .AppendTypesConstantsGlobals(tc.used_consts)
+      .AppendInMain(tc.main_insts);
+  const std::string expected = builder.GetCode();
+  builder.AppendTypesConstantsGlobals(tc.dead_consts);
+  builder.PrependPreamble(tc.checks);
+  const std::string assembly_with_dead_const = builder.GetCode();
+
+  // Do not enable validation. As the input code is invalid from the base
+  // tests (ported from other passes).
+  SinglePassRunAndMatch<opt::AggressiveDCEPass>(assembly_with_dead_const,
+                                                false);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    ScalarTypeConstants, EliminateDeadConstantTest,
+    ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+        // clang-format off
+        // Scalar type constants, one dead constant and one used constant.
+        {
+            /* .used_consts = */
+            {
+              "%used_const_int = OpConstant %int 1",
+            },
+            /* .main_insts = */
+            {
+              "%int_var = OpVariable %_pf_int Output",
+              "OpStore %int_var %used_const_int",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_const_int = OpConstant %int 1",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[const:%\\w+]] = OpConstant %int 1",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[const]]",
+            },
+        },
+        {
+            /* .used_consts = */
+            {
+              "%used_const_uint = OpConstant %uint 1",
+            },
+            /* .main_insts = */
+            {
+              "%uint_var = OpVariable %_pf_uint Output",
+              "OpStore %uint_var %used_const_uint",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_const_uint = OpConstant %uint 1",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[const:%\\w+]] = OpConstant %uint 1",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[const]]",
+            },
+        },
+        {
+            /* .used_consts = */
+            {
+              "%used_const_float = OpConstant %float 3.14",
+            },
+            /* .main_insts = */
+            {
+              "%float_var = OpVariable %_pf_float Output",
+              "OpStore %float_var %used_const_float",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_const_float = OpConstant %float 3.14",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[const:%\\w+]] = OpConstant %float 3.14",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[const]]",
+            },
+        },
+        {
+            /* .used_consts = */
+            {
+              "%used_const_double = OpConstant %double 3.14",
+            },
+            /* .main_insts = */
+            {
+              "%double_var = OpVariable %_pf_double Output",
+              "OpStore %double_var %used_const_double",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_const_double = OpConstant %double 3.14",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[const:%\\w+]] = OpConstant %double 3.14",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[const]]",
+            },
+        },
+        // clang-format on
+    })));
+
+INSTANTIATE_TEST_CASE_P(
+    VectorTypeConstants, EliminateDeadConstantTest,
+    ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+        // clang-format off
+        // Tests eliminating dead constant type ivec2. One dead constant vector
+        // and one used constant vector, each built from its own group of
+        // scalar constants.
+        {
+            /* .used_consts = */
+            {
+              "%used_int_x = OpConstant %int 1",
+              "%used_int_y = OpConstant %int 2",
+              "%used_v2int = OpConstantComposite %v2int %used_int_x %used_int_y",
+            },
+            /* .main_insts = */
+            {
+              "%v2int_var = OpVariable %_pf_v2int Output",
+              "OpStore %v2int_var %used_v2int",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_int_x = OpConstant %int 1",
+              "%dead_int_y = OpConstant %int 2",
+              "%dead_v2int = OpConstantComposite %v2int %dead_int_x %dead_int_y",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[constx:%\\w+]] = OpConstant %int 1",
+              "; CHECK: [[consty:%\\w+]] = OpConstant %int 2",
+              "; CHECK: [[const:%\\w+]] = OpConstantComposite %v2int [[constx]] [[consty]]",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[const]]",
+            },
+        },
+        // Tests eliminating dead constant ivec3. One dead constant vector and
+        // one used constant vector. But both built from a same group of
+        // scalar constants.
+        {
+            /* .used_consts = */
+            {
+              "%used_int_x = OpConstant %int 1",
+              "%used_int_y = OpConstant %int 2",
+              "%used_int_z = OpConstant %int 3",
+              "%used_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z",
+            },
+            /* .main_insts = */
+            {
+              "%v3int_var = OpVariable %_pf_v3int Output",
+              "OpStore %v3int_var %used_v3int",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_v3int = OpConstantComposite %v3int %used_int_x %used_int_y %used_int_z",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[constx:%\\w+]] = OpConstant %int 1",
+              "; CHECK: [[consty:%\\w+]] = OpConstant %int 2",
+              "; CHECK: [[constz:%\\w+]] = OpConstant %int 3",
+              "; CHECK: [[const:%\\w+]] = OpConstantComposite %v3int [[constx]] [[consty]] [[constz]]",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[const]]",
+            },
+        },
+        // Tests eliminating dead constant vec2. One dead constant vector and
+        // one used constant vector. Each built from its own group of scalar
+        // constants.
+        {
+            /* .used_consts = */
+            {
+              "%used_float_x = OpConstant %float 3.14",
+              "%used_float_y = OpConstant %float 4.13",
+              "%used_v2float = OpConstantComposite %v2float %used_float_x %used_float_y",
+            },
+            /* .main_insts = */
+            {
+              "%v2float_var = OpVariable %_pf_v2float Output",
+              "OpStore %v2float_var %used_v2float",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_float_x = OpConstant %float 3.14",
+              "%dead_float_y = OpConstant %float 4.13",
+              "%dead_v2float = OpConstantComposite %v2float %dead_float_x %dead_float_y",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[constx:%\\w+]] = OpConstant %float 3.14",
+              "; CHECK: [[consty:%\\w+]] = OpConstant %float 4.13",
+              "; CHECK: [[const:%\\w+]] = OpConstantComposite %v2float [[constx]] [[consty]]",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[const]]",
+            },
+        },
+        // Tests eliminating dead constant vec3. One dead constant vector and
+        // one used constant vector. Both built from a same group of scalar
+        // constants.
+        {
+            /* .used_consts = */
+            {
+              "%used_float_x = OpConstant %float 3.14",
+              "%used_float_y = OpConstant %float 4.13",
+              "%used_float_z = OpConstant %float 4.31",
+              "%used_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z",
+            },
+            /* .main_insts = */
+            {
+              "%v3float_var = OpVariable %_pf_v3float Output",
+              "OpStore %v3float_var %used_v3float",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_v3float = OpConstantComposite %v3float %used_float_x %used_float_y %used_float_z",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[constx:%\\w+]] = OpConstant %float 3.14",
+              "; CHECK: [[consty:%\\w+]] = OpConstant %float 4.13",
+              "; CHECK: [[constz:%\\w+]] = OpConstant %float 4.31",
+              "; CHECK: [[const:%\\w+]] = OpConstantComposite %v3float [[constx]] [[consty]]",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[const]]",
+            },
+        },
+        // clang-format on
+    })));
+
+INSTANTIATE_TEST_CASE_P(
+    StructTypeConstants, EliminateDeadConstantTest,
+    ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+        // clang-format off
+        // A plain struct type dead constants. All of its components are dead
+        // constants too.
+        {
+            /* .used_consts = */ {},
+            /* .main_insts = */ {},
+            /* .dead_consts = */
+            {
+              "%dead_bool = OpConstantTrue %bool",
+              "%dead_int = OpConstant %int 1",
+              "%dead_float = OpConstant %float 2.5",
+              "%dead_double = OpConstant %double 3.14159265358979",
+              "%dead_struct = OpConstantComposite %flat_struct %dead_bool %dead_int %dead_float %dead_double",
+            },
+            /* .checks = */
+            {
+              "; CHECK-NOT: OpConstant",
+            },
+        },
+        // A plain struct type dead constants. Some of its components are dead
+        // constants while others are not.
+        {
+            /* .used_consts = */
+            {
+                "%used_int = OpConstant %int 1",
+                "%used_double = OpConstant %double 3.14159265358979",
+            },
+            /* .main_insts = */
+            {
+                "%int_var = OpVariable %_pf_int Output",
+                "OpStore %int_var %used_int",
+                "%double_var = OpVariable %_pf_double Output",
+                "OpStore %double_var %used_double",
+            },
+            /* .dead_consts = */
+            {
+                "%dead_bool = OpConstantTrue %bool",
+                "%dead_float = OpConstant %float 2.5",
+                "%dead_struct = OpConstantComposite %flat_struct %dead_bool %used_int %dead_float %used_double",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[int:%\\w+]] = OpConstant %int 1",
+              "; CHECK: [[double:%\\w+]] = OpConstant %double 3.14159265358979",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[int]]",
+              "; CHECK: OpStore {{%\\w+}} [[double]]",
+            },
+        },
+        // A nesting struct type dead constants. All components of both outer
+        // and inner structs are dead and should be removed after dead constant
+        // elimination.
+        {
+            /* .used_consts = */ {},
+            /* .main_insts = */ {},
+            /* .dead_consts = */
+            {
+              "%dead_bool = OpConstantTrue %bool",
+              "%dead_int = OpConstant %int 1",
+              "%dead_float = OpConstant %float 2.5",
+              "%dead_double = OpConstant %double 3.1415926535",
+              "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %dead_int %dead_float %dead_double",
+              "%dead_int2 = OpConstant %int 2",
+              "%dead_double2 = OpConstant %double 1.428571428514",
+              "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int2 %dead_double2",
+            },
+            /* .checks = */
+            {
+              "; CHECK-NOT: OpConstant",
+            },
+        },
+        // A nesting struct type dead constants. Some of its components are
+        // dead constants while others are not.
+        {
+            /* .used_consts = */
+            {
+              "%used_int = OpConstant %int 1",
+              "%used_double = OpConstant %double 3.14159265358979",
+            },
+            /* .main_insts = */
+            {
+              "%int_var = OpVariable %_pf_int Output",
+              "OpStore %int_var %used_int",
+              "%double_var = OpVariable %_pf_double Output",
+              "OpStore %double_var %used_double",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_bool = OpConstantTrue %bool",
+              "%dead_float = OpConstant %float 2.5",
+              "%dead_inner_struct = OpConstantComposite %inner_struct %dead_bool %used_int %dead_float %used_double",
+              "%dead_int = OpConstant %int 2",
+              "%dead_outer_struct = OpConstantComposite %outer_struct %dead_inner_struct %dead_int %used_double",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[int:%\\w+]] = OpConstant %int 1",
+              "; CHECK: [[double:%\\w+]] = OpConstant %double 3.14159265358979",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[int]]",
+              "; CHECK: OpStore {{%\\w+}} [[double]]",
+            },
+        },
+        // A nesting struct case. The inner struct is used while the outer struct is not
+        {
+          /* .used_const = */
+          {
+            "%used_bool = OpConstantTrue %bool",
+            "%used_int = OpConstant %int 1",
+            "%used_float = OpConstant %float 1.23",
+            "%used_double = OpConstant %double 1.2345678901234",
+            "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double",
+          },
+          /* .main_insts = */
+          {
+            "%bool_var = OpVariable %_pf_bool Output",
+            "%bool_from_inner_struct = OpCompositeExtract %bool %used_inner_struct 0",
+            "OpStore %bool_var %bool_from_inner_struct",
+          },
+          /* .dead_consts = */
+          {
+            "%dead_int = OpConstant %int 2",
+            "%dead_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %dead_int %used_double"
+          },
+          /* .checks = */
+          {
+            "; CHECK: [[bool:%\\w+]] = OpConstantTrue",
+            "; CHECK: [[int:%\\w+]] = OpConstant %int 1",
+            "; CHECK: [[float:%\\w+]] = OpConstant %float 1.23",
+            "; CHECK: [[double:%\\w+]] = OpConstant %double 1.2345678901234",
+            "; CHECK: [[struct:%\\w+]] = OpConstantComposite %inner_struct [[bool]] [[int]] [[float]] [[double]]",
+            "; CHECK-NOT: OpConstant",
+            "; CHECK: OpCompositeExtract %bool [[struct]]",
+          }
+        },
+        // A nesting struct case. The outer struct is used, so the inner struct should not
+        // be removed even though it is not used anywhere.
+        {
+          /* .used_const = */
+          {
+            "%used_bool = OpConstantTrue %bool",
+            "%used_int = OpConstant %int 1",
+            "%used_float = OpConstant %float 1.23",
+            "%used_double = OpConstant %double 1.2345678901234",
+            "%used_inner_struct = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double",
+            "%used_outer_struct = OpConstantComposite %outer_struct %used_inner_struct %used_int %used_double"
+          },
+          /* .main_insts = */
+          {
+            "%int_var = OpVariable %_pf_int Output",
+            "%int_from_outer_struct = OpCompositeExtract %int %used_outer_struct 1",
+            "OpStore %int_var %int_from_outer_struct",
+          },
+          /* .dead_consts = */ {},
+          /* .checks = */
+          {
+            "; CHECK: [[bool:%\\w+]] = OpConstantTrue %bool",
+            "; CHECK: [[int:%\\w+]] = OpConstant %int 1",
+            "; CHECK: [[float:%\\w+]] = OpConstant %float 1.23",
+            "; CHECK: [[double:%\\w+]] = OpConstant %double 1.2345678901234",
+            "; CHECK: [[inner_struct:%\\w+]] = OpConstantComposite %inner_struct %used_bool %used_int %used_float %used_double",
+            "; CHECK: [[outer_struct:%\\w+]] = OpConstantComposite %outer_struct %used_inner_struct %used_int %used_double",
+            "; CHECK: OpCompositeExtract %int [[outer_struct]]",
+          },
+        },
+        // clang-format on
+    })));
+
+INSTANTIATE_TEST_CASE_P(
+    ScalarTypeSpecConstants, EliminateDeadConstantTest,
+    ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+        // clang-format off
+        // All scalar type spec constants.
+        {
+            /* .used_consts = */
+            {
+              "%used_bool = OpSpecConstantTrue %bool",
+              "%used_uint = OpSpecConstant %uint 2",
+              "%used_int = OpSpecConstant %int 2",
+              "%used_float = OpSpecConstant %float 2.5",
+              "%used_double = OpSpecConstant %double 1.428571428514",
+            },
+            /* .main_insts = */
+            {
+              "%bool_var = OpVariable %_pf_bool Output",
+              "%uint_var = OpVariable %_pf_uint Output",
+              "%int_var = OpVariable %_pf_int Output",
+              "%float_var = OpVariable %_pf_float Output",
+              "%double_var = OpVariable %_pf_double Output",
+              "OpStore %bool_var %used_bool",
+              "OpStore %uint_var %used_uint",
+              "OpStore %int_var %used_int",
+              "OpStore %float_var %used_float",
+              "OpStore %double_var %used_double",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_bool = OpSpecConstantTrue %bool",
+              "%dead_uint = OpSpecConstant %uint 2",
+              "%dead_int = OpSpecConstant %int 2",
+              "%dead_float = OpSpecConstant %float 2.5",
+              "%dead_double = OpSpecConstant %double 1.428571428514",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[bool:%\\w+]] = OpSpecConstantTrue %bool",
+              "; CHECK: [[uint:%\\w+]] = OpSpecConstant %uint 2",
+              "; CHECK: [[int:%\\w+]] = OpSpecConstant %int 2",
+              "; CHECK: [[float:%\\w+]] = OpSpecConstant %float 2.5",
+              "; CHECK: [[double:%\\w+]] = OpSpecConstant %double 1.428571428514",
+              "; CHECK-NOT: OpSpecConstant",
+              "; CHECK: OpStore {{%\\w+}} [[bool]]",
+              "; CHECK: OpStore {{%\\w+}} [[uint]]",
+              "; CHECK: OpStore {{%\\w+}} [[int]]",
+              "; CHECK: OpStore {{%\\w+}} [[float]]",
+              "; CHECK: OpStore {{%\\w+}} [[double]]",
+            },
+        },
+        // clang-format on
+    })));
+
+INSTANTIATE_TEST_CASE_P(
+    VectorTypeSpecConstants, EliminateDeadConstantTest,
+    ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+        // clang-format off
+        // Bool vector type spec constants. One vector has all component dead,
+        // another vector has one dead boolean and one used boolean.
+        {
+            /* .used_consts = */
+            {
+              "%used_bool = OpSpecConstantTrue %bool",
+            },
+            /* .main_insts = */
+            {
+              "%bool_var = OpVariable %_pf_bool Output",
+              "OpStore %bool_var %used_bool",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_bool = OpSpecConstantFalse %bool",
+              "%dead_bool_vec1 = OpSpecConstantComposite %v2bool %dead_bool %dead_bool",
+              "%dead_bool_vec2 = OpSpecConstantComposite %v2bool %dead_bool %used_bool",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[bool:%\\w+]] = OpSpecConstantTrue %bool",
+              "; CHECK-NOT: OpSpecConstant",
+              "; CHECK: OpStore {{%\\w+}} [[bool]]",
+            },
+        },
+
+        // Uint vector type spec constants. One vector has all component dead,
+        // another vector has one dead unsigend integer and one used unsigned
+        // integer.
+        {
+            /* .used_consts = */
+            {
+              "%used_uint = OpSpecConstant %uint 3",
+            },
+            /* .main_insts = */
+            {
+              "%uint_var = OpVariable %_pf_uint Output",
+              "OpStore %uint_var %used_uint",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_uint = OpSpecConstant %uint 1",
+              "%dead_uint_vec1 = OpSpecConstantComposite %v2uint %dead_uint %dead_uint",
+              "%dead_uint_vec2 = OpSpecConstantComposite %v2uint %dead_uint %used_uint",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[uint:%\\w+]] = OpSpecConstant %uint 3",
+              "; CHECK-NOT: OpSpecConstant",
+              "; CHECK: OpStore {{%\\w+}} [[uint]]",
+            },
+        },
+
+        // Int vector type spec constants. One vector has all component dead,
+        // another vector has one dead integer and one used integer.
+        {
+            /* .used_consts = */
+            {
+              "%used_int = OpSpecConstant %int 3",
+            },
+            /* .main_insts = */
+            {
+              "%int_var = OpVariable %_pf_int Output",
+              "OpStore %int_var %used_int",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_int = OpSpecConstant %int 1",
+              "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_int %dead_int",
+              "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_int %used_int",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[int:%\\w+]] = OpSpecConstant %int 3",
+              "; CHECK-NOT: OpSpecConstant",
+              "; CHECK: OpStore {{%\\w+}} [[int]]",
+            },
+        },
+
+        // Int vector type spec constants built with both spec constants and
+        // front-end constants.
+        {
+            /* .used_consts = */
+            {
+              "%used_spec_int = OpSpecConstant %int 3",
+              "%used_front_end_int = OpConstant %int 3",
+            },
+            /* .main_insts = */
+            {
+              "%int_var1 = OpVariable %_pf_int Output",
+              "OpStore %int_var1 %used_spec_int",
+              "%int_var2 = OpVariable %_pf_int Output",
+              "OpStore %int_var2 %used_front_end_int",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_spec_int = OpSpecConstant %int 1",
+              "%dead_front_end_int = OpConstant %int 1",
+              // Dead front-end and dead spec constants
+              "%dead_int_vec1 = OpSpecConstantComposite %v2int %dead_spec_int %dead_front_end_int",
+              // Used front-end and dead spec constants
+              "%dead_int_vec2 = OpSpecConstantComposite %v2int %dead_spec_int %used_front_end_int",
+              // Dead front-end and used spec constants
+              "%dead_int_vec3 = OpSpecConstantComposite %v2int %dead_front_end_int %used_spec_int",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[int1:%\\w+]] = OpSpecConstant %int 3",
+              "; CHECK: [[int2:%\\w+]] = OpConstant %int 3",
+              "; CHECK-NOT: OpSpecConstant",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK: OpStore {{%\\w+}} [[int1]]",
+              "; CHECK: OpStore {{%\\w+}} [[int2]]",
+            },
+        },
+        // clang-format on
+    })));
+
+INSTANTIATE_TEST_CASE_P(
+    SpecConstantOp, EliminateDeadConstantTest,
+    ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+        // clang-format off
+        // Cast operations: uint <-> int <-> bool
+        {
+            /* .used_consts = */ {},
+            /* .main_insts = */ {},
+            /* .dead_consts = */
+            {
+              // Assistant constants, only used in dead spec constant
+              // operations.
+              "%signed_zero = OpConstant %int 0",
+              "%signed_zero_vec = OpConstantComposite %v2int %signed_zero %signed_zero",
+              "%unsigned_zero = OpConstant %uint 0",
+              "%unsigned_zero_vec = OpConstantComposite %v2uint %unsigned_zero %unsigned_zero",
+              "%signed_one = OpConstant %int 1",
+              "%signed_one_vec = OpConstantComposite %v2int %signed_one %signed_one",
+              "%unsigned_one = OpConstant %uint 1",
+              "%unsigned_one_vec = OpConstantComposite %v2uint %unsigned_one %unsigned_one",
+
+              // Spec constants that support casting to each other.
+              "%dead_bool = OpSpecConstantTrue %bool",
+              "%dead_uint = OpSpecConstant %uint 1",
+              "%dead_int = OpSpecConstant %int 2",
+              "%dead_bool_vec = OpSpecConstantComposite %v2bool %dead_bool %dead_bool",
+              "%dead_uint_vec = OpSpecConstantComposite %v2uint %dead_uint %dead_uint",
+              "%dead_int_vec = OpSpecConstantComposite %v2int %dead_int %dead_int",
+
+              // Scalar cast to boolean spec constant.
+              "%int_to_bool = OpSpecConstantOp %bool INotEqual %dead_int %signed_zero",
+              "%uint_to_bool = OpSpecConstantOp %bool INotEqual %dead_uint %unsigned_zero",
+
+              // Vector cast to boolean spec constant.
+              "%int_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_int_vec %signed_zero_vec",
+              "%uint_to_bool_vec = OpSpecConstantOp %v2bool INotEqual %dead_uint_vec %unsigned_zero_vec",
+
+              // Scalar cast to int spec constant.
+              "%bool_to_int = OpSpecConstantOp %int Select %dead_bool %signed_one %signed_zero",
+              "%uint_to_int = OpSpecConstantOp %uint IAdd %dead_uint %unsigned_zero",
+
+              // Vector cast to int spec constant.
+              "%bool_to_int_vec = OpSpecConstantOp %v2int Select %dead_bool_vec %signed_one_vec %signed_zero_vec",
+              "%uint_to_int_vec = OpSpecConstantOp %v2uint IAdd %dead_uint_vec %unsigned_zero_vec",
+
+              // Scalar cast to uint spec constant.
+              "%bool_to_uint = OpSpecConstantOp %uint Select %dead_bool %unsigned_one %unsigned_zero",
+              "%int_to_uint_vec = OpSpecConstantOp %uint IAdd %dead_int %signed_zero",
+
+              // Vector cast to uint spec constant.
+              "%bool_to_uint_vec = OpSpecConstantOp %v2uint Select %dead_bool_vec %unsigned_one_vec %unsigned_zero_vec",
+              "%int_to_uint = OpSpecConstantOp %v2uint IAdd %dead_int_vec %signed_zero_vec",
+            },
+            /* .checks = */
+            {
+              "; CHECK-NOT: OpConstant",
+              "; CHECK-NOT: OpSpecConstant",
+            },
+        },
+
+        // Add, sub, mul, div, rem.
+        {
+            /* .used_consts = */ {},
+            /* .main_insts = */ {},
+            /* .dead_consts = */
+            {
+              "%dead_spec_int_a = OpSpecConstant %int 1",
+              "%dead_spec_int_a_vec = OpSpecConstantComposite %v2int %dead_spec_int_a %dead_spec_int_a",
+
+              "%dead_spec_int_b = OpSpecConstant %int 2",
+              "%dead_spec_int_b_vec = OpSpecConstantComposite %v2int %dead_spec_int_b %dead_spec_int_b",
+
+              "%dead_const_int_c = OpConstant %int 3",
+              "%dead_const_int_c_vec = OpConstantComposite %v2int %dead_const_int_c %dead_const_int_c",
+
+              // Add
+              "%add_a_b = OpSpecConstantOp %int IAdd %dead_spec_int_a %dead_spec_int_b",
+              "%add_a_b_vec = OpSpecConstantOp %v2int IAdd %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+              // Sub
+              "%sub_a_b = OpSpecConstantOp %int ISub %dead_spec_int_a %dead_spec_int_b",
+              "%sub_a_b_vec = OpSpecConstantOp %v2int ISub %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+              // Mul
+              "%mul_a_b = OpSpecConstantOp %int IMul %dead_spec_int_a %dead_spec_int_b",
+              "%mul_a_b_vec = OpSpecConstantOp %v2int IMul %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+              // Div
+              "%div_a_b = OpSpecConstantOp %int SDiv %dead_spec_int_a %dead_spec_int_b",
+              "%div_a_b_vec = OpSpecConstantOp %v2int SDiv %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+              // Bitwise Xor
+              "%xor_a_b = OpSpecConstantOp %int BitwiseXor %dead_spec_int_a %dead_spec_int_b",
+              "%xor_a_b_vec = OpSpecConstantOp %v2int BitwiseXor %dead_spec_int_a_vec %dead_spec_int_b_vec",
+
+              // Scalar Comparison
+              "%less_a_b = OpSpecConstantOp %bool SLessThan %dead_spec_int_a %dead_spec_int_b",
+            },
+            /* .checks = */
+            {
+              "; CHECK-NOT: OpConstant",
+              "; CHECK-NOT: OpSpecConstant",
+            },
+        },
+
+        // Vectors without used swizzles should be removed.
+        {
+            /* .used_consts = */
+            {
+              "%used_int = OpConstant %int 3",
+            },
+            /* .main_insts = */
+            {
+              "%int_var = OpVariable %_pf_int Output",
+              "OpStore %int_var %used_int",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_int = OpConstant %int 3",
+
+              "%dead_spec_int_a = OpSpecConstant %int 1",
+              "%vec_a = OpSpecConstantComposite %v4int %dead_spec_int_a %dead_spec_int_a %dead_int %dead_int",
+
+              "%dead_spec_int_b = OpSpecConstant %int 2",
+              "%vec_b = OpSpecConstantComposite %v4int %dead_spec_int_b %dead_spec_int_b %used_int %used_int",
+
+              // Extract scalar
+              "%a_x = OpSpecConstantOp %int CompositeExtract %vec_a 0",
+              "%b_x = OpSpecConstantOp %int CompositeExtract %vec_b 0",
+
+              // Extract vector
+              "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1",
+              "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1",
+            },
+            /* .checks = */
+            {
+              "; CHECK: [[int:%\\w+]] = OpConstant %int 3",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK-NOT: OpSpecConstant",
+              "; CHECK: OpStore {{%\\w+}} [[int]]",
+            },
+        },
+        // Vectors with used swizzles should not be removed.
+        {
+            /* .used_consts = */
+            {
+              "%used_int = OpConstant %int 3",
+              "%used_spec_int_a = OpSpecConstant %int 1",
+              "%used_spec_int_b = OpSpecConstant %int 2",
+              // Create vectors
+              "%vec_a = OpSpecConstantComposite %v4int %used_spec_int_a %used_spec_int_a %used_int %used_int",
+              "%vec_b = OpSpecConstantComposite %v4int %used_spec_int_b %used_spec_int_b %used_int %used_int",
+              // Extract vector
+              "%a_xy = OpSpecConstantOp %v2int VectorShuffle %vec_a %vec_a 0 1",
+              "%b_xy = OpSpecConstantOp %v2int VectorShuffle %vec_b %vec_b 0 1",
+            },
+            /* .main_insts = */
+            {
+              "%v2int_var_a = OpVariable %_pf_v2int Output",
+              "%v2int_var_b = OpVariable %_pf_v2int Output",
+              "OpStore %v2int_var_a %a_xy",
+              "OpStore %v2int_var_b %b_xy",
+            },
+            /* .dead_consts = */ {},
+            /* .checks = */
+            {
+              "; CHECK: [[int:%\\w+]] = OpConstant %int 3",
+              "; CHECK: [[a:%\\w+]] = OpSpecConstant %int 1",
+              "; CHECK: [[b:%\\w+]] = OpSpecConstant %int 2",
+              "; CHECK: [[veca:%\\w+]] = OpSpecConstantComposite %v4int [[a]] [[a]] [[int]] [[int]]",
+              "; CHECK: [[vecb:%\\w+]] = OpSpecConstantComposite %v4int [[b]] [[b]] [[int]] [[int]]",
+              "; CHECK: [[exa:%\\w+]] = OpSpecConstantOp %v2int VectorShuffle [[veca]] [[veca]] 0 1",
+              "; CHECK: [[exb:%\\w+]] = OpSpecConstantOp %v2int VectorShuffle [[vecb]] [[vecb]] 0 1",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK-NOT: OpSpecConstant",
+              "; CHECK: OpStore {{%\\w+}} [[exa]]",
+              "; CHECK: OpStore {{%\\w+}} [[exb]]",
+            },
+        },
+        // clang-format on
+    })));
+
+INSTANTIATE_TEST_CASE_P(
+    LongDefUseChain, EliminateDeadConstantTest,
+    ::testing::ValuesIn(std::vector<EliminateDeadConstantTestCase>({
+        // clang-format off
+        // Long Def-Use chain with binary operations.
+        {
+            /* .used_consts = */
+            {
+              "%array_size = OpConstant %int 4",
+              "%type_arr_int_4 = OpTypeArray %int %array_size",
+              "%used_int_0 = OpConstant %int 100",
+              "%used_int_1 = OpConstant %int 1",
+              "%used_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_1",
+              "%used_int_3 = OpSpecConstantOp %int ISub %used_int_0 %used_int_2",
+              "%used_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_3",
+              "%used_int_5 = OpSpecConstantOp %int ISub %used_int_0 %used_int_4",
+              "%used_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_5",
+              "%used_int_7 = OpSpecConstantOp %int ISub %used_int_0 %used_int_6",
+              "%used_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_7",
+              "%used_int_9 = OpSpecConstantOp %int ISub %used_int_0 %used_int_8",
+              "%used_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_9",
+              "%used_int_11 = OpSpecConstantOp %int ISub %used_int_0 %used_int_10",
+              "%used_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_11",
+              "%used_int_13 = OpSpecConstantOp %int ISub %used_int_0 %used_int_12",
+              "%used_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_13",
+              "%used_int_15 = OpSpecConstantOp %int ISub %used_int_0 %used_int_14",
+              "%used_int_16 = OpSpecConstantOp %int ISub %used_int_0 %used_int_15",
+              "%used_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_16",
+              "%used_int_18 = OpSpecConstantOp %int ISub %used_int_0 %used_int_17",
+              "%used_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %used_int_18",
+              "%used_int_20 = OpSpecConstantOp %int ISub %used_int_0 %used_int_19",
+              "%used_vec_a = OpSpecConstantComposite %v2int %used_int_18 %used_int_19",
+              "%used_vec_b = OpSpecConstantOp %v2int IMul %used_vec_a %used_vec_a",
+              "%used_int_21 = OpSpecConstantOp %int CompositeExtract %used_vec_b 0",
+              "%used_array = OpConstantComposite %type_arr_int_4 %used_int_20 %used_int_20 %used_int_21 %used_int_21",
+            },
+            /* .main_insts = */
+            {
+              "%int_var = OpVariable %_pf_int Output",
+              "%used_array_2 = OpCompositeExtract %int %used_array 2",
+              "OpStore %int_var %used_array_2",
+            },
+            /* .dead_consts = */
+            {
+              "%dead_int_1 = OpConstant %int 2",
+              "%dead_int_2 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_1",
+              "%dead_int_3 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_2",
+              "%dead_int_4 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_3",
+              "%dead_int_5 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_4",
+              "%dead_int_6 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_5",
+              "%dead_int_7 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_6",
+              "%dead_int_8 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_7",
+              "%dead_int_9 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_8",
+              "%dead_int_10 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_9",
+              "%dead_int_11 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_10",
+              "%dead_int_12 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_11",
+              "%dead_int_13 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_12",
+              "%dead_int_14 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_13",
+              "%dead_int_15 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_14",
+              "%dead_int_16 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_15",
+              "%dead_int_17 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_16",
+              "%dead_int_18 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_17",
+              "%dead_int_19 = OpSpecConstantOp %int IAdd %used_int_0 %dead_int_18",
+              "%dead_int_20 = OpSpecConstantOp %int ISub %used_int_0 %dead_int_19",
+              "%dead_vec_a = OpSpecConstantComposite %v2int %dead_int_18 %dead_int_19",
+              "%dead_vec_b = OpSpecConstantOp %v2int IMul %dead_vec_a %dead_vec_a",
+              "%dead_int_21 = OpSpecConstantOp %int CompositeExtract %dead_vec_b 0",
+              "%dead_array = OpConstantComposite %type_arr_int_4 %dead_int_20 %used_int_20 %dead_int_19 %used_int_19",
+            },
+            /* .checks = */
+            {
+              "; CHECK: OpConstant %int 4",
+              "; CHECK: [[array:%\\w+]] = OpConstantComposite %type_arr_int_4 %used_int_20 %used_int_20 %used_int_21 %used_int_21",
+              "; CHECK-NOT: OpConstant",
+              "; CHECK-NOT: OpSpecConstant",
+              "; CHECK: OpStore {{%\\w+}} [[array]]",
+            },
+        },
+        // Long Def-Use chain with swizzle
+        // clang-format on
+    })));
+
+TEST_F(AggressiveDCETest, DeadDecorationGroup) {
+  // The decoration group should be eliminated because the target of group
+  // decorate is dead.
+  const std::string text = R"(
+; CHECK-NOT: OpDecorat
+; CHECK-NOT: OpGroupDecorate
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %var
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr = OpTypePointer Function %uint
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr Function
+OpReturn
+OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<opt::AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroup) {
+  const std::string text = R"(
+; CHECK: OpDecorate [[grp:%\w+]] Restrict
+; CHECK: OpDecorate [[grp]] Aliased
+; CHECK: [[grp]] = OpDecorationGroup
+; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]]
+; CHECK: [[output]] = OpVariable {{%\w+}} Output
+; CHECK-NOT: OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %var %output
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr_Function = OpTypePointer Function %uint
+%uint_ptr_Output = OpTypePointer Output %uint
+%uint_0 = OpConstant %uint 0
+%output = OpVariable %uint_ptr_Output Output
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr_Function Function
+OpStore %output %uint_0
+OpReturn
+OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<opt::AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, ParitallyDeadDecorationGroupDifferentGroupDecorate) {
+  const std::string text = R"(
+; CHECK: OpDecorate [[grp:%\w+]] Restrict
+; CHECK: OpDecorate [[grp]] Aliased
+; CHECK: [[grp]] = OpDecorationGroup
+; CHECK: OpGroupDecorate [[grp]] [[output:%\w+]]
+; CHECK-NOT: OpGroupDecorate
+; CHECK: [[output]] = OpVariable {{%\w+}} Output
+; CHECK-NOT: OpVariable {{%\w+}} Function
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Restrict
+OpDecorate %1 Aliased
+%1 = OpDecorationGroup
+OpGroupDecorate %1 %output
+OpGroupDecorate %1 %var
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%uint_ptr_Function = OpTypePointer Function %uint
+%uint_ptr_Output = OpTypePointer Output %uint
+%uint_0 = OpConstant %uint 0
+%output = OpVariable %uint_ptr_Output Output
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %uint_ptr_Function Function
+OpStore %output %uint_0
+OpReturn
+OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<opt::AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, DeadGroupMemberDecorate) {
+  const std::string text = R"(
+; CHECK-NOT: OpDec
+; CHECK-NOT: OpGroup
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main"
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Offset 0
+OpDecorate %1 Uniform
+%1 = OpDecorationGroup
+OpGroupMemberDecorate %1 %var 0
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%struct = OpTypeStruct %uint %uint
+%struct_ptr = OpTypePointer Function %struct
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct_ptr Function
+OpReturn
+OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<opt::AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest, PartiallyDeadGroupMemberDecorate) {
+  const std::string text = R"(
+; CHECK: OpDecorate [[grp:%\w+]] Offset 0
+; CHECK: OpDecorate [[grp]] Uniform
+; CHECK: [[grp]] = OpDecorationGroup
+; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1
+; CHECK: [[output]] = OpTypeStruct
+; CHECK-NOT: OpTypeStruct
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Offset 0
+OpDecorate %1 Uniform
+%1 = OpDecorationGroup
+OpGroupMemberDecorate %1 %var_struct 0 %output_struct 1
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%var_struct = OpTypeStruct %uint %uint
+%output_struct = OpTypeStruct %uint %uint
+%struct_ptr_Function = OpTypePointer Function %var_struct
+%struct_ptr_Output = OpTypePointer Output %output_struct
+%uint_ptr_Output = OpTypePointer Output %uint
+%output = OpVariable %struct_ptr_Output Output
+%uint_0 = OpConstant %uint 0
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct_ptr_Function Function
+%3 = OpAccessChain %uint_ptr_Output %output %uint_0
+OpStore %3 %uint_0
+OpReturn
+OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<opt::AggressiveDCEPass>(text, true);
+}
+
+TEST_F(AggressiveDCETest,
+       PartiallyDeadGroupMemberDecorateDifferentGroupDecorate) {
+  const std::string text = R"(
+; CHECK: OpDecorate [[grp:%\w+]] Offset 0
+; CHECK: OpDecorate [[grp]] Uniform
+; CHECK: [[grp]] = OpDecorationGroup
+; CHECK: OpGroupMemberDecorate [[grp]] [[output:%\w+]] 1
+; CHECK-NOT: OpGroupMemberDecorate
+; CHECK: [[output]] = OpTypeStruct
+; CHECK-NOT: OpTypeStruct
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint Fragment %main "main" %output
+OpExecutionMode %main OriginUpperLeft
+OpDecorate %1 Offset 0
+OpDecorate %1 Uniform
+%1 = OpDecorationGroup
+OpGroupMemberDecorate %1 %var_struct 0
+OpGroupMemberDecorate %1 %output_struct 1
+%void = OpTypeVoid
+%func = OpTypeFunction %void
+%uint = OpTypeInt 32 0
+%var_struct = OpTypeStruct %uint %uint
+%output_struct = OpTypeStruct %uint %uint
+%struct_ptr_Function = OpTypePointer Function %var_struct
+%struct_ptr_Output = OpTypePointer Output %output_struct
+%uint_ptr_Output = OpTypePointer Output %uint
+%output = OpVariable %struct_ptr_Output Output
+%uint_0 = OpConstant %uint 0
+%main = OpFunction %void None %func
+%2 = OpLabel
+%var = OpVariable %struct_ptr_Function Function
+%3 = OpAccessChain %uint_ptr_Output %output %uint_0
+OpStore %3 %uint_0
+OpReturn
+OpFunctionEnd
+  )";
+
+  SinglePassRunAndMatch<opt::AggressiveDCEPass>(text, true);
+}
+#endif  // SPIRV_EFFCEE
+
 // TODO(greg-lunarg): Add tests to verify handling of these cases:
 //
 //    Check that logical addressing required
index e1e87e1..2edd4a5 100644 (file)
@@ -154,9 +154,18 @@ class AssemblyBuilder {
     return *this;
   }
 
+  // Pre-pends string to the preamble of the module. Useful for EFFCEE checks.
+  AssemblyBuilder& PrependPreamble(const std::vector<std::string>& preamble) {
+    preamble_.insert(preamble_.end(), preamble.begin(), preamble.end());
+    return *this;
+  }
+
   // Get the SPIR-V assembly code as string.
   std::string GetCode() const {
     std::ostringstream ss;
+    for (const auto& line : preamble_) {
+      ss << line << std::endl;
+    }
     for (const auto& line : global_preamble_) {
       ss << line << std::endl;
     }
@@ -230,6 +239,8 @@ class AssemblyBuilder {
   }
 
   uint32_t spec_id_counter_;
+  // User-defined preamble.
+  std::vector<std::string> preamble_;
   // The vector that contains common preambles shared across all test SPIR-V
   // code.
   std::vector<std::string> global_preamble_;