Add MessageConsumer to PassManager, Pass, and analysis interfaces.

Also convert some uses of assert() in optimization code to use
SPIRV_ASSERT(), SPIRV_UNIMPLEMENTED(), or SPIRV_UNREACHABLE()
accordingly.
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index a045ce1..e999e6a 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -143,6 +143,7 @@
   ${CMAKE_CURRENT_SOURCE_DIR}/disassemble.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/ext_inst.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/instruction.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/message.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/name_mapper.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/opcode.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/operand.cpp
diff --git a/source/message.cpp b/source/message.cpp
new file mode 100644
index 0000000..9d28c8c
--- /dev/null
+++ b/source/message.cpp
@@ -0,0 +1,55 @@
+// Copyright (c) 2016 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "message.h"
+
+#include <sstream>
+
+namespace spvtools {
+
+std::string StringifyMessage(MessageLevel level, const char* source,
+                             const spv_position_t& position,
+                             const char* message) {
+  const char* level_string = nullptr;
+  switch (level) {
+    case MessageLevel::Fatal:
+      level_string = "fatal";
+      break;
+    case MessageLevel::InternalError:
+      level_string = "internal error";
+      break;
+    case MessageLevel::Error:
+      level_string = "error";
+      break;
+    case MessageLevel::Warning:
+      level_string = "warning";
+      break;
+    case MessageLevel::Info:
+      level_string = "info";
+      break;
+    case MessageLevel::Debug:
+      level_string = "debug";
+      break;
+  }
+  std::ostringstream oss;
+  oss << level_string << ": ";
+  if (source) oss << source << ":";
+  oss << position.line << ":" << position.column << ":";
+  oss << position.index << ": ";
+  if (message) oss << message;
+  oss << "\n";
+  return oss.str();
+}
+
+}  // namespace spvtools
diff --git a/source/message.h b/source/message.h
index 68e4cd7..7af4297 100644
--- a/source/message.h
+++ b/source/message.h
@@ -16,6 +16,7 @@
 #define SPIRV_TOOLS_MESSAGE_H_
 
 #include <functional>
+#include <string>
 
 #include "spirv-tools/libspirv.h"
 
@@ -42,6 +43,17 @@
     const spv_position_t& /* position */, const char* /* message */
     )>;
 
+// A message consumer that ignores all messages.
+inline void IgnoreMessage(MessageLevel, const char*, const spv_position_t&,
+                          const char*) {}
+
+// A helper function to compose and return a string from the message in the
+// following format:
+//   "<level>: <source>:<line>:<column>:<index>: <message>"
+std::string StringifyMessage(MessageLevel level, const char* source,
+                             const spv_position_t& position,
+                             const char* message);
+
 }  // namespace spvtools
 
 #endif  // SPIRV_TOOLS_MESSAGE_H_
diff --git a/source/opt/def_use_manager.cpp b/source/opt/def_use_manager.cpp
index e829c5f..19178eb 100644
--- a/source/opt/def_use_manager.cpp
+++ b/source/opt/def_use_manager.cpp
@@ -14,10 +14,10 @@
 
 #include "def_use_manager.h"
 
-#include <cassert>
 #include <functional>
 
 #include "instruction.h"
+#include "log.h"
 #include "module.h"
 #include "reflect.h"
 
@@ -80,11 +80,10 @@
   return &iter->second;
 }
 
-std::vector<ir::Instruction*> DefUseManager::GetAnnotations(
-    uint32_t id) const {
+std::vector<ir::Instruction*> DefUseManager::GetAnnotations(uint32_t id) const {
   std::vector<ir::Instruction*> annos;
   const auto* uses = GetUses(id);
-  if (!uses)  return annos;
+  if (!uses) return annos;
   for (const auto& c : *uses) {
     if (ir::IsAnnotationInst(c.inst->opcode())) {
       annos.push_back(c.inst);
@@ -121,11 +120,13 @@
       if (it->inst->type_id() != 0 && it->operand_index == 0) {
         it->inst->SetResultType(after);
       } else if (it->inst->type_id() == 0) {
-        assert(false &&
-               "Result type id considered as using while the instruction "
-               "doesn't have a result type id.");
+        SPIRV_ASSERT(consumer_, false,
+                     "Result type id considered as use while the instruction "
+                     "doesn't have a result type id.");
+        (void)consumer_;  // Makes the compiler happy for release build.
       } else {
-        assert(false && "Trying Setting the result id which is immutable.");
+        SPIRV_ASSERT(consumer_, false,
+                     "Trying setting the immutable result id.");
       }
     } else {
       // Update an in-operand.
diff --git a/source/opt/def_use_manager.h b/source/opt/def_use_manager.h
index 23f098a..41c405a 100644
--- a/source/opt/def_use_manager.h
+++ b/source/opt/def_use_manager.h
@@ -21,6 +21,7 @@
 #include <vector>
 
 #include "instruction.h"
+#include "message.h"
 #include "module.h"
 
 namespace spvtools {
@@ -45,7 +46,15 @@
   using IdToDefMap = std::unordered_map<uint32_t, ir::Instruction*>;
   using IdToUsesMap = std::unordered_map<uint32_t, UseList>;
 
-  inline explicit DefUseManager(ir::Module* module) { AnalyzeDefUse(module); }
+  // Constructs a def-use manager from the given |module|. All internal messages
+  // will be communicated to the outside via the given message |consumer|. This
+  // instance only keeps a reference to the |consumer|, so the |consumer| should
+  // outlive this instance.
+  DefUseManager(const MessageConsumer& consumer, ir::Module* module)
+      : consumer_(consumer) {
+    AnalyzeDefUse(module);
+  }
+
   DefUseManager(const DefUseManager&) = delete;
   DefUseManager(DefUseManager&&) = delete;
   DefUseManager& operator=(const DefUseManager&) = delete;
@@ -106,8 +115,9 @@
   // Erases the records that a given instruction uses its operand ids.
   void EraseUseRecordsOfOperandIds(const ir::Instruction* inst);
 
-  IdToDefMap id_to_def_;    // Mapping from ids to their definitions
-  IdToUsesMap id_to_uses_;  // Mapping from ids to their uses
+  const MessageConsumer& consumer_;  // Message consumer.
+  IdToDefMap id_to_def_;             // Mapping from ids to their definitions
+  IdToUsesMap id_to_uses_;           // Mapping from ids to their uses
   // Mapping from instructions to the ids used in the instructions generating
   // the result ids.
   InstToUsedIdsMap inst_to_used_ids_;
diff --git a/source/opt/eliminate_dead_constant_pass.cpp b/source/opt/eliminate_dead_constant_pass.cpp
index 15f1e44..c7a921f 100644
--- a/source/opt/eliminate_dead_constant_pass.cpp
+++ b/source/opt/eliminate_dead_constant_pass.cpp
@@ -19,13 +19,14 @@
 #include <unordered_set>
 
 #include "def_use_manager.h"
+#include "log.h"
 #include "reflect.h"
 
 namespace spvtools {
 namespace opt {
 
 bool EliminateDeadConstantPass::Process(ir::Module* module) {
-  analysis::DefUseManager def_use(module);
+  analysis::DefUseManager def_use(consumer(), module);
   std::unordered_set<ir::Instruction*> working_list;
   // Traverse all the instructions to get the initial set of dead constants as
   // working list and count number of real uses for constants. Uses in
@@ -73,7 +74,7 @@
           }
           // The number of uses should never be less then 0, so it can not be
           // less than 1 before it decreases.
-          assert(use_counts[def_inst] > 0);
+          SPIRV_ASSERT(consumer(), use_counts[def_inst] > 0);
           --use_counts[def_inst];
           if (!use_counts[def_inst]) {
             working_list.insert(def_inst);
diff --git a/source/opt/eliminate_dead_constant_pass.h b/source/opt/eliminate_dead_constant_pass.h
index fa09dba..bc24eba 100644
--- a/source/opt/eliminate_dead_constant_pass.h
+++ b/source/opt/eliminate_dead_constant_pass.h
@@ -28,6 +28,8 @@
 // OpSpecConstantOp.
 class EliminateDeadConstantPass : public Pass {
  public:
+  explicit EliminateDeadConstantPass(const MessageConsumer& c) : Pass(c) {}
+
   const char* name() const override { return "eliminate-dead-const"; }
   bool Process(ir::Module*) override;
 };
diff --git a/source/opt/fold_spec_constant_op_and_composite_pass.cpp b/source/opt/fold_spec_constant_op_and_composite_pass.cpp
index 1d5ffc2..6adb497 100644
--- a/source/opt/fold_spec_constant_op_and_composite_pass.cpp
+++ b/source/opt/fold_spec_constant_op_and_composite_pass.cpp
@@ -242,6 +242,15 @@
 }
 }  // anonymous namespace
 
+FoldSpecConstantOpAndCompositePass::FoldSpecConstantOpAndCompositePass(
+    const MessageConsumer& c)
+    : Pass(c),
+      max_id_(0),
+      module_(nullptr),
+      def_use_mgr_(nullptr),
+      type_mgr_(nullptr),
+      id_to_const_val_() {}
+
 bool FoldSpecConstantOpAndCompositePass::ProcessImpl(ir::Module* module) {
   bool modified = false;
   // Traverse through all the constant defining instructions. For Normal
diff --git a/source/opt/fold_spec_constant_op_and_composite_pass.h b/source/opt/fold_spec_constant_op_and_composite_pass.h
index e7205f3..8b913a3 100644
--- a/source/opt/fold_spec_constant_op_and_composite_pass.h
+++ b/source/opt/fold_spec_constant_op_and_composite_pass.h
@@ -50,12 +50,8 @@
 // TODO(qining): Add support for the operations listed above.
 class FoldSpecConstantOpAndCompositePass : public Pass {
  public:
-  FoldSpecConstantOpAndCompositePass()
-      : max_id_(0),
-        module_(nullptr),
-        def_use_mgr_(nullptr),
-        type_mgr_(nullptr),
-        id_to_const_val_() {}
+  explicit FoldSpecConstantOpAndCompositePass(const MessageConsumer& c);
+
   const char* name() const override { return "fold-spec-const-op-composite"; }
   bool Process(ir::Module* module) override {
     Initialize(module);
@@ -66,8 +62,8 @@
   // Initializes the type manager, def-use manager and get the maximal id used
   // in the module.
   void Initialize(ir::Module* module) {
-    type_mgr_.reset(new analysis::TypeManager(*module));
-    def_use_mgr_.reset(new analysis::DefUseManager(module));
+    type_mgr_.reset(new analysis::TypeManager(consumer(), *module));
+    def_use_mgr_.reset(new analysis::DefUseManager(consumer(), module));
     for (const auto& id_def : def_use_mgr_->id_to_defs()) {
       max_id_ = std::max(max_id_, id_def.first);
     }
diff --git a/source/opt/freeze_spec_constant_value_pass.h b/source/opt/freeze_spec_constant_value_pass.h
index efe98e7..8b21463 100644
--- a/source/opt/freeze_spec_constant_value_pass.h
+++ b/source/opt/freeze_spec_constant_value_pass.h
@@ -31,6 +31,8 @@
 // other spec constants defined by OpSpecConstantComposite or OpSpecConstantOp.
 class FreezeSpecConstantValuePass : public Pass {
  public:
+  explicit FreezeSpecConstantValuePass(const MessageConsumer& c) : Pass(c) {}
+
   const char* name() const override { return "freeze-spec-const"; }
   bool Process(ir::Module*) override;
 };
diff --git a/source/opt/ir_loader.cpp b/source/opt/ir_loader.cpp
index 3066a87..09106a3 100644
--- a/source/opt/ir_loader.cpp
+++ b/source/opt/ir_loader.cpp
@@ -14,8 +14,7 @@
 
 #include "ir_loader.h"
 
-#include <cassert>
-
+#include "log.h"
 #include "reflect.h"
 
 namespace spvtools {
@@ -34,28 +33,28 @@
   // Handle function and basic block boundaries first, then normal
   // instructions.
   if (opcode == SpvOpFunction) {
-    assert(function_ == nullptr);
-    assert(block_ == nullptr);
+    SPIRV_ASSERT(consumer_, function_ == nullptr);
+    SPIRV_ASSERT(consumer_, block_ == nullptr);
     function_.reset(new Function(std::move(spv_inst)));
   } else if (opcode == SpvOpFunctionEnd) {
-    assert(function_ != nullptr);
-    assert(block_ == nullptr);
+    SPIRV_ASSERT(consumer_, function_ != nullptr);
+    SPIRV_ASSERT(consumer_, block_ == nullptr);
     function_->SetFunctionEnd(std::move(spv_inst));
     module_->AddFunction(std::move(function_));
     function_ = nullptr;
   } else if (opcode == SpvOpLabel) {
-    assert(function_ != nullptr);
-    assert(block_ == nullptr);
+    SPIRV_ASSERT(consumer_, function_ != nullptr);
+    SPIRV_ASSERT(consumer_, block_ == nullptr);
     block_.reset(new BasicBlock(std::move(spv_inst)));
   } else if (IsTerminatorInst(opcode)) {
-    assert(function_ != nullptr);
-    assert(block_ != nullptr);
+    SPIRV_ASSERT(consumer_, function_ != nullptr);
+    SPIRV_ASSERT(consumer_, block_ != nullptr);
     block_->AddInstruction(std::move(spv_inst));
     function_->AddBasicBlock(std::move(block_));
     block_ = nullptr;
   } else {
     if (function_ == nullptr) {  // Outside function definition
-      assert(block_ == nullptr);
+      SPIRV_ASSERT(consumer_, block_ == nullptr);
       if (opcode == SpvOpCapability) {
         module_->AddCapability(std::move(spv_inst));
       } else if (opcode == SpvOpExtension) {
@@ -78,11 +77,12 @@
                  opcode == SpvOpUndef) {
         module_->AddGlobalValue(std::move(spv_inst));
       } else {
-        assert(0 && "unhandled inst type outside function defintion");
+        SPIRV_UNIMPLEMENTED(consumer_,
+                            "unhandled inst type outside function defintion");
       }
     } else {
       if (block_ == nullptr) {  // Inside function but outside blocks
-        assert(opcode == SpvOpFunctionParameter);
+        SPIRV_ASSERT(consumer_, opcode == SpvOpFunctionParameter);
         function_->AddParameter(std::move(spv_inst));
       } else {
         block_->AddInstruction(std::move(spv_inst));
diff --git a/source/opt/ir_loader.h b/source/opt/ir_loader.h
index 6795a5d..ce971d0 100644
--- a/source/opt/ir_loader.h
+++ b/source/opt/ir_loader.h
@@ -19,6 +19,7 @@
 
 #include "basic_block.h"
 #include "instruction.h"
+#include "message.h"
 #include "module.h"
 #include "spirv-tools/libspirv.h"
 
@@ -36,7 +37,11 @@
 class IrLoader {
  public:
   // Instantiates a builder to construct the given |module| gradually.
-  IrLoader(Module* module) : module_(module) {}
+  // All internal messages will be communicated to the outside via the given
+  // message |consumer|. This instance only keeps a reference to the |consumer|,
+  // so the |consumer| should outlive this instance.
+  IrLoader(const MessageConsumer& consumer, Module* module)
+      : consumer_(consumer), module_(module) {}
 
   // Sets the fields in the module's header to the given parameters.
   void SetModuleHeader(uint32_t magic, uint32_t version, uint32_t generator,
@@ -54,6 +59,8 @@
   void EndModule();
 
  private:
+  // Consumer for communicating messages to outside.
+  const MessageConsumer& consumer_;
   // The module to be built.
   Module* module_;
   // The current Function under construction.
diff --git a/source/opt/libspirv.cpp b/source/opt/libspirv.cpp
index eabc260..47be681 100644
--- a/source/opt/libspirv.cpp
+++ b/source/opt/libspirv.cpp
@@ -83,7 +83,7 @@
   spv_diagnostic diagnostic = nullptr;
 
   auto module = MakeUnique<ir::Module>();
-  ir::IrLoader loader(module.get());
+  ir::IrLoader loader(context_->consumer, module.get());
 
   spv_result_t status =
       spvBinaryParse(context_, &loader, binary.data(), binary.size(),
diff --git a/source/opt/null_pass.h b/source/opt/null_pass.h
index 0f87c61..7e10791 100644
--- a/source/opt/null_pass.h
+++ b/source/opt/null_pass.h
@@ -23,6 +23,9 @@
 
 // A null pass that does nothing.
 class NullPass : public Pass {
+ public:
+  explicit NullPass(const MessageConsumer& c) : Pass(c) {}
+
   const char* name() const override { return "null"; }
   bool Process(ir::Module*) override { return false; }
 };
diff --git a/source/opt/pass.h b/source/opt/pass.h
index 22df24e..dadb53a 100644
--- a/source/opt/pass.h
+++ b/source/opt/pass.h
@@ -17,6 +17,7 @@
 
 #include <memory>
 
+#include "message.h"
 #include "module.h"
 
 namespace spvtools {
@@ -26,11 +27,24 @@
 // and all analysis and transformation is done via the Process() method.
 class Pass {
  public:
+  // Constructs a new pass with the given message consumer, which will be
+  // invoked every time there is a message to be communicated to the outside.
+  //
+  // This pass just keeps a reference to the message consumer; so the message
+  // consumer should outlive this pass.
+  explicit Pass(const MessageConsumer& c) : consumer_(c) {}
+
   // Returns a descriptive name for this pass.
   virtual const char* name() const = 0;
+  // Returns the reference to the message consumer for this pass.
+  const MessageConsumer& consumer() const { return consumer_; }
+
   // Processes the given |module| and returns true if the given |module| is
   // modified for optimization.
   virtual bool Process(ir::Module* module) = 0;
+
+ private:
+  const MessageConsumer& consumer_;  // Message consumer.
 };
 
 }  // namespace opt
diff --git a/source/opt/pass_manager.h b/source/opt/pass_manager.h
index 6242a46..58c0537 100644
--- a/source/opt/pass_manager.h
+++ b/source/opt/pass_manager.h
@@ -15,10 +15,11 @@
 #ifndef LIBSPIRV_OPT_PASS_MANAGER_H_
 #define LIBSPIRV_OPT_PASS_MANAGER_H_
 
-#include <cassert>
 #include <memory>
 #include <vector>
 
+#include "log.h"
+#include "message.h"
 #include "module.h"
 #include "passes.h"
 
@@ -27,32 +28,35 @@
 
 // The pass manager, responsible for tracking and running passes.
 // Clients should first call AddPass() to add passes and then call Run()
-// to run on a module. Passes are executed in the exact order of added.
-//
-// TODO(antiagainst): The pass manager is fairly simple right now. Eventually it
-// should support pass dependency, common functionality (like def-use analysis)
-// sharing, etc.
+// to run on a module. Passes are executed in the exact order of addition.
 class PassManager {
  public:
-  // Adds a pass.
+  // Constructs a pass manager with the given message consumer.
+  explicit PassManager(MessageConsumer c) : consumer_(std::move(c)) {}
+
+  // Adds an externally constructed pass.
   void AddPass(std::unique_ptr<Pass> pass) {
     passes_.push_back(std::move(pass));
   }
-  // Uses the argument to construct a pass instance of type PassT, and adds the
-  // pass instance to this pass manger.
-  template <typename PassT, typename... Args>
+  // Uses the argument |args| to construct a pass instance of type |T|, and adds
+  // the pass instance to this pass manger. The pass added will use this pass
+  // manager's message consumer.
+  template <typename T, typename... Args>
   void AddPass(Args&&... args) {
-    passes_.emplace_back(new PassT(std::forward<Args>(args)...));
+    passes_.emplace_back(new T(consumer_, std::forward<Args>(args)...));
   }
 
   // Returns the number of passes added.
   uint32_t NumPasses() const { return static_cast<uint32_t>(passes_.size()); }
   // Returns a pointer to the |index|th pass added.
   Pass* GetPass(uint32_t index) const {
-    assert(index < passes_.size() && "index out of bound");
+    SPIRV_ASSERT(consumer_, index < passes_.size(), "index out of bound");
     return passes_[index].get();
   }
 
+  // Returns the message consumer.
+  const MessageConsumer& consumer() const { return consumer_; }
+
   // Runs all passes on the given |module|.
   void Run(ir::Module* module) {
     bool modified = false;
@@ -64,6 +68,8 @@
   }
 
  private:
+  // Consumer for messages.
+  MessageConsumer consumer_;
   // A vector of passes. Order matters.
   std::vector<std::unique_ptr<Pass>> passes_;
 };
diff --git a/source/opt/set_spec_constant_default_value_pass.cpp b/source/opt/set_spec_constant_default_value_pass.cpp
index 9ea304d..68db8bf 100644
--- a/source/opt/set_spec_constant_default_value_pass.cpp
+++ b/source/opt/set_spec_constant_default_value_pass.cpp
@@ -14,8 +14,8 @@
 
 #include "set_spec_constant_default_value_pass.h"
 
-#include <cstring>
 #include <cctype>
+#include <cstring>
 #include <string>
 #include <tuple>
 #include <unordered_map>
@@ -164,8 +164,8 @@
   const uint32_t kOpSpecConstantLiteralInOperandIndex = 0;
 
   bool modified = false;
-  analysis::DefUseManager def_use_mgr(module);
-  analysis::TypeManager type_mgr(*module);
+  analysis::DefUseManager def_use_mgr(consumer(), module);
+  analysis::TypeManager type_mgr(consumer(), *module);
   // Scan through all the annotation instructions to find 'OpDecorate SpecId'
   // instructions. Then extract the decoration target of those instructions.
   // The decoration targets should be spec constant defining instructions with
diff --git a/source/opt/set_spec_constant_default_value_pass.h b/source/opt/set_spec_constant_default_value_pass.h
index 0958f19..7762050 100644
--- a/source/opt/set_spec_constant_default_value_pass.h
+++ b/source/opt/set_spec_constant_default_value_pass.h
@@ -34,11 +34,12 @@
   using SpecIdToInstMap = std::unordered_map<uint32_t, ir::Instruction*>;
 
   // Constructs a pass instance with a map from spec ids to default values.
-  explicit SetSpecConstantDefaultValuePass(
-      const SpecIdToValueStrMap& default_values)
-      : Pass(), spec_id_to_value_(default_values) {}
-  explicit SetSpecConstantDefaultValuePass(SpecIdToValueStrMap&& default_values)
-      : Pass(), spec_id_to_value_(std::move(default_values)) {}
+  SetSpecConstantDefaultValuePass(const MessageConsumer& c,
+                                  const SpecIdToValueStrMap& default_values)
+      : Pass(c), spec_id_to_value_(default_values) {}
+  SetSpecConstantDefaultValuePass(const MessageConsumer& c,
+                                  SpecIdToValueStrMap&& default_values)
+      : Pass(c), spec_id_to_value_(std::move(default_values)) {}
 
   const char* name() const override { return "set-spec-const-default-value"; }
   bool Process(ir::Module*) override;
diff --git a/source/opt/strip_debug_info_pass.h b/source/opt/strip_debug_info_pass.h
index 2bf3512..9a8d27c 100644
--- a/source/opt/strip_debug_info_pass.h
+++ b/source/opt/strip_debug_info_pass.h
@@ -25,6 +25,8 @@
 // Section 3.32.2 of the SPIR-V spec).
 class StripDebugInfoPass : public Pass {
  public:
+  explicit StripDebugInfoPass(const MessageConsumer& c) : Pass(c) {}
+
   const char* name() const override { return "strip-debug"; }
   bool Process(ir::Module* module) override;
 };
diff --git a/source/opt/type_manager.cpp b/source/opt/type_manager.cpp
index f55cecc..ce4b676 100644
--- a/source/opt/type_manager.cpp
+++ b/source/opt/type_manager.cpp
@@ -13,8 +13,8 @@
 // limitations under the License.
 
 #include <algorithm>
-#include <cassert>
 
+#include "log.h"
 #include "reflect.h"
 #include "type_manager.h"
 
@@ -161,16 +161,17 @@
       type = new NamedBarrier();
       break;
     default:
-      assert(0 && "unhandled type found");
+      SPIRV_UNIMPLEMENTED(consumer_, "unhandled type");
       break;
   }
 
   uint32_t id = inst.result_id();
   if (id == 0) {
-    assert(inst.opcode() == SpvOpTypeForwardPointer &&
-           "instruction without result id found");
+    SPIRV_ASSERT(consumer_, inst.opcode() == SpvOpTypeForwardPointer,
+                 "instruction without result id found");
   } else {
-    assert(type != nullptr && "type should not be nullptr at this point");
+    SPIRV_ASSERT(consumer_, type != nullptr,
+                 "type should not be nullptr at this point");
     id_to_type_[id].reset(type);
     type_to_id_[type] = id;
   }
@@ -204,16 +205,16 @@
       if (Struct* st = target_type->AsStruct()) {
         st->AddMemeberDecoration(index, std::move(data));
       } else {
-        assert(0 && "OpMemberDecorate on non-struct type");
+        SPIRV_UNIMPLEMENTED(consumer_, "OpMemberDecorate non-struct type");
       }
     } break;
     case SpvOpDecorationGroup:
     case SpvOpGroupDecorate:
     case SpvOpGroupMemberDecorate:
-      assert(0 && "unhandled decoration");
+      SPIRV_UNIMPLEMENTED(consumer_, "unhandled decoration");
       break;
     default:
-      assert(0 && "unreachable");
+      SPIRV_UNREACHABLE(consumer_);
       break;
   }
 }
diff --git a/source/opt/type_manager.h b/source/opt/type_manager.h
index a8a8155..48fa212 100644
--- a/source/opt/type_manager.h
+++ b/source/opt/type_manager.h
@@ -19,6 +19,7 @@
 #include <unordered_map>
 #include <unordered_set>
 
+#include "message.h"
 #include "module.h"
 #include "types.h"
 
@@ -33,7 +34,13 @@
   using TypeToIdMap = std::unordered_map<const Type*, uint32_t>;
   using ForwardPointerVector = std::vector<std::unique_ptr<ForwardPointer>>;
 
-  inline explicit TypeManager(const spvtools::ir::Module& module);
+  // Constructs a type manager from the given |module|. All internal messages
+  // will be communicated to the outside via the given message |consumer|.
+  // This instance only keeps a reference to the |consumer|, so the |consumer|
+  // should outlive this instance.
+  TypeManager(const MessageConsumer& consumer,
+              const spvtools::ir::Module& module);
+
   TypeManager(const TypeManager&) = delete;
   TypeManager(TypeManager&&) = delete;
   TypeManager& operator=(const TypeManager&) = delete;
@@ -64,6 +71,7 @@
   // given instruction is not a decoration instruction or not decorating a type.
   void AttachIfTypeDecoration(const spvtools::ir::Instruction& inst);
 
+  const MessageConsumer& consumer_;  // Message consumer.
   IdToTypeMap id_to_type_;  // Mapping from ids to their type representations.
   TypeToIdMap type_to_id_;  // Mapping from types to their defining ids.
   ForwardPointerVector forward_pointers_;  // All forward pointer declarations.
@@ -72,7 +80,9 @@
   std::unordered_set<ForwardPointer*> unresolved_forward_pointers_;
 };
 
-inline TypeManager::TypeManager(const spvtools::ir::Module& module) {
+inline TypeManager::TypeManager(const spvtools::MessageConsumer& consumer,
+                                const spvtools::ir::Module& module)
+    : consumer_(consumer) {
   AnalyzeTypes(module);
 }
 
diff --git a/source/opt/unify_const_pass.cpp b/source/opt/unify_const_pass.cpp
index 02a5c9c..72fb3cd 100644
--- a/source/opt/unify_const_pass.cpp
+++ b/source/opt/unify_const_pass.cpp
@@ -105,7 +105,7 @@
 bool UnifyConstantPass::Process(ir::Module* module) {
   bool modified = false;
   ResultIdTrie defined_constants;
-  analysis::DefUseManager def_use_mgr(module);
+  analysis::DefUseManager def_use_mgr(consumer(), module);
 
   for (ir::Instruction& inst : module->types_values()) {
     // Do not handle the instruction when there are decorations upon the result
diff --git a/source/opt/unify_const_pass.h b/source/opt/unify_const_pass.h
index 6debaad..db17b69 100644
--- a/source/opt/unify_const_pass.h
+++ b/source/opt/unify_const_pass.h
@@ -36,6 +36,8 @@
 //  3) NaN in float point format with different bit patterns are not unified.
 class UnifyConstantPass : public Pass {
  public:
+  explicit UnifyConstantPass(const MessageConsumer& c) : Pass(c) {}
+
   const char* name() const override { return "unify-const"; }
   bool Process(ir::Module*) override;
 };
@@ -43,4 +45,4 @@
 }  // namespace opt
 }  // namespace spvtools
 
-#endif // LIBSPIRV_OPT_UNIFY_CONSTANT_PASS_H_
+#endif  // LIBSPIRV_OPT_UNIFY_CONSTANT_PASS_H_
diff --git a/test/opt/pass_fixture.h b/test/opt/pass_fixture.h
index ec8d569..436ed42 100644
--- a/test/opt/pass_fixture.h
+++ b/test/opt/pass_fixture.h
@@ -15,6 +15,7 @@
 #ifndef LIBSPIRV_TEST_OPT_PASS_FIXTURE_H_
 #define LIBSPIRV_TEST_OPT_PASS_FIXTURE_H_
 
+#include <iostream>
 #include <string>
 #include <tuple>
 #include <vector>
@@ -39,7 +40,9 @@
 class PassTest : public TestT {
  public:
   PassTest()
-      : tools_(SPV_ENV_UNIVERSAL_1_1), manager_(new opt::PassManager()) {}
+      : consumer_(IgnoreMessage),
+        tools_(SPV_ENV_UNIVERSAL_1_1),
+        manager_(new opt::PassManager(consumer_)) {}
 
   // Runs the given |pass| on the binary assembled from the |assembly|, and
   // disassebles the optimized binary. Returns a tuple of disassembly string
@@ -70,7 +73,7 @@
   template <typename PassT, typename... Args>
   std::tuple<std::string, bool> SinglePassRunAndDisassemble(
       const std::string& assembly, bool skip_nop, Args&&... args) {
-    auto pass = MakeUnique<PassT>(std::forward<Args>(args)...);
+    auto pass = MakeUnique<PassT>(consumer_, std::forward<Args>(args)...);
     return OptimizeAndDisassemble(pass.get(), assembly, skip_nop);
   }
 
@@ -98,7 +101,7 @@
   }
 
   // Renews the pass manager, including clearing all previously added passes.
-  void RenewPassManger() { manager_.reset(new opt::PassManager()); }
+  void RenewPassManger() { manager_.reset(new opt::PassManager(consumer_)); }
 
   // Runs the passes added thus far using a pass manager on the binary assembled
   // from the |original| assembly, and checks whether the optimized binary can
@@ -121,6 +124,7 @@
   }
 
  private:
+  MessageConsumer consumer_;  // Message consumer.
   SpvTools tools_;  // An instance for calling SPIRV-Tools functionalities.
   std::unique_ptr<opt::PassManager> manager_;  // The pass manager.
 };
diff --git a/test/opt/test_def_use.cpp b/test/opt/test_def_use.cpp
index 2f29c08..412bf64 100644
--- a/test/opt/test_def_use.cpp
+++ b/test/opt/test_def_use.cpp
@@ -132,7 +132,7 @@
   ASSERT_NE(nullptr, module);
 
   // Analyze def and use.
-  opt::analysis::DefUseManager manager(module.get());
+  opt::analysis::DefUseManager manager(IgnoreMessage, module.get());
 
   CheckDef(tc.du, manager.id_to_defs());
   CheckUse(tc.du, manager.id_to_uses());
@@ -513,7 +513,7 @@
   ASSERT_NE(nullptr, module);
 
   // Analyze def and use.
-  opt::analysis::DefUseManager manager(module.get());
+  opt::analysis::DefUseManager manager(IgnoreMessage, module.get());
 
   // Do the substitution.
   for (const auto& candiate : tc.candidates) {
@@ -815,7 +815,7 @@
   ASSERT_NE(nullptr, module);
 
   // Analyze def and use.
-  opt::analysis::DefUseManager manager(module.get());
+  opt::analysis::DefUseManager manager(IgnoreMessage, module.get());
 
   // Do the substitution.
   for (const auto id : tc.ids_to_kill) manager.KillDef(id);
@@ -1065,7 +1065,7 @@
   ASSERT_NE(nullptr, module);
 
   // Analyze def and use.
-  opt::analysis::DefUseManager manager(module.get());
+  opt::analysis::DefUseManager manager(IgnoreMessage, module.get());
 
   // Do a bunch replacements.
   manager.ReplaceAllUsesWith(9, 900);    // to unused id
@@ -1188,7 +1188,7 @@
   ASSERT_NE(nullptr, module);
 
   // Analyze the instructions.
-  opt::analysis::DefUseManager manager(module.get());
+  opt::analysis::DefUseManager manager(IgnoreMessage, module.get());
   for (ir::Instruction& inst : tc.insts) {
     manager.AnalyzeInstDefUse(&inst);
   }
@@ -1312,7 +1312,7 @@
 
   // KillInst
   uint32_t index = 0;
-  opt::analysis::DefUseManager manager(module.get());
+  opt::analysis::DefUseManager manager(IgnoreMessage, module.get());
   module->ForEachInst([&index, &tc, &manager](ir::Instruction* inst) {
     if (tc.indices_for_inst_to_kill.count(index) != 0) {
       manager.KillInst(inst);
@@ -1419,7 +1419,7 @@
   ASSERT_NE(nullptr, module);
 
   // Get annotations
-  opt::analysis::DefUseManager manager(module.get());
+  opt::analysis::DefUseManager manager(IgnoreMessage, module.get());
   auto insts = manager.GetAnnotations(tc.id);
 
   // Check
diff --git a/test/opt/test_line_debug_info.cpp b/test/opt/test_line_debug_info.cpp
index 81eed2b..3e86ed0 100644
--- a/test/opt/test_line_debug_info.cpp
+++ b/test/opt/test_line_debug_info.cpp
@@ -22,6 +22,8 @@
 // A pass turning all none debug line instructions into Nop.
 class NopifyPass : public opt::Pass {
  public:
+  explicit NopifyPass(const MessageConsumer& c) : opt::Pass(c) {}
+
   const char* name() const override { return "NopifyPass"; }
   bool Process(ir::Module* module) override {
     module->ForEachInst([](ir::Instruction* inst) { inst->ToNop(); },
diff --git a/test/opt/test_pass_manager.cpp b/test/opt/test_pass_manager.cpp
index cf653c2..b365ad2 100644
--- a/test/opt/test_pass_manager.cpp
+++ b/test/opt/test_pass_manager.cpp
@@ -29,23 +29,25 @@
 // A null pass whose construtors accept arguments
 class NullPassWithArgs : public opt::NullPass {
  public:
-  NullPassWithArgs(uint32_t) : NullPass() {}
-  NullPassWithArgs(std::string) : NullPass() {}
-  NullPassWithArgs(const std::vector<int>&) : NullPass() {}
-  NullPassWithArgs(const std::vector<int>&, uint32_t) : NullPass() {}
+  NullPassWithArgs(const MessageConsumer& c, uint32_t) : NullPass(c) {}
+  NullPassWithArgs(const MessageConsumer& c, std::string) : NullPass(c) {}
+  NullPassWithArgs(const MessageConsumer& c, const std::vector<int>&)
+      : NullPass(c) {}
+  NullPassWithArgs(const MessageConsumer& c, const std::vector<int>&, uint32_t)
+      : NullPass(c) {}
 
   const char* name() const override { return "null-with-args"; }
 };
 
 TEST(PassManager, Interface) {
-  opt::PassManager manager;
+  opt::PassManager manager(IgnoreMessage);
   EXPECT_EQ(0u, manager.NumPasses());
 
   manager.AddPass<opt::StripDebugInfoPass>();
   EXPECT_EQ(1u, manager.NumPasses());
   EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
 
-  manager.AddPass(MakeUnique<opt::NullPass>());
+  manager.AddPass(MakeUnique<opt::NullPass>(IgnoreMessage));
   EXPECT_EQ(2u, manager.NumPasses());
   EXPECT_STREQ("strip-debug", manager.GetPass(0)->name());
   EXPECT_STREQ("null", manager.GetPass(1)->name());
@@ -72,6 +74,9 @@
 
 // A pass that appends an OpNop instruction to the debug section.
 class AppendOpNopPass : public opt::Pass {
+ public:
+  explicit AppendOpNopPass(const MessageConsumer& c) : opt::Pass(c) {}
+
   const char* name() const override { return "AppendOpNop"; }
   bool Process(ir::Module* module) override {
     auto inst = MakeUnique<ir::Instruction>();
@@ -84,7 +89,9 @@
 // section.
 class AppendMultipleOpNopPass : public opt::Pass {
  public:
-  AppendMultipleOpNopPass(uint32_t num_nop) : num_nop_(num_nop) {}
+  AppendMultipleOpNopPass(const MessageConsumer& c, uint32_t num_nop)
+      : opt::Pass(c), num_nop_(num_nop) {}
+
   const char* name() const override { return "AppendOpNop"; }
   bool Process(ir::Module* module) override {
     for (uint32_t i = 0; i < num_nop_; i++) {
@@ -100,6 +107,9 @@
 
 // A pass that duplicates the last instruction in the debug section.
 class DuplicateInstPass : public opt::Pass {
+ public:
+  explicit DuplicateInstPass(const MessageConsumer& c) : opt::Pass(c) {}
+
   const char* name() const override { return "DuplicateInst"; }
   bool Process(ir::Module* module) override {
     auto inst = MakeUnique<ir::Instruction>(*(--module->debug_end()));
@@ -135,7 +145,9 @@
 // A pass that appends an OpTypeVoid instruction that uses a given id.
 class AppendTypeVoidInstPass : public opt::Pass {
  public:
-  AppendTypeVoidInstPass(uint32_t result_id) : result_id_(result_id) {}
+  AppendTypeVoidInstPass(const MessageConsumer& c, uint32_t result_id)
+      : opt::Pass(c), result_id_(result_id) {}
+
   const char* name() const override { return "AppendTypeVoidInstPass"; }
   bool Process(ir::Module* module) override {
     auto inst = MakeUnique<ir::Instruction>(SpvOpTypeVoid, 0, result_id_,
@@ -152,14 +164,14 @@
   ir::Module module;
   EXPECT_THAT(GetIdBound(module), Eq(0u));
 
-  opt::PassManager manager;
+  opt::PassManager manager(IgnoreMessage);
   manager.Run(&module);
   manager.AddPass<AppendOpNopPass>();
   // With no ID changes, the ID bound does not change.
   EXPECT_THAT(GetIdBound(module), Eq(0u));
 
   // Now we force an Id of 100 to be used.
-  manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(100));
+  manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(IgnoreMessage, 100));
   EXPECT_THAT(GetIdBound(module), Eq(0u));
   manager.Run(&module);
   // The Id has been updated automatically, even though the pass
@@ -167,12 +179,12 @@
   EXPECT_THAT(GetIdBound(module), Eq(101u));
 
   // Try one more time!
-  manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(200));
+  manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(IgnoreMessage, 200));
   manager.Run(&module);
   EXPECT_THAT(GetIdBound(module), Eq(201u));
 
   // Add another pass, but which uses a lower Id.
-  manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(10));
+  manager.AddPass(MakeUnique<AppendTypeVoidInstPass>(IgnoreMessage, 10));
   manager.Run(&module);
   // The Id stays high.
   EXPECT_THAT(GetIdBound(module), Eq(201u));
diff --git a/test/opt/test_type_manager.cpp b/test/opt/test_type_manager.cpp
index 7120a81..1fb2776 100644
--- a/test/opt/test_type_manager.cpp
+++ b/test/opt/test_type_manager.cpp
@@ -90,7 +90,7 @@
 
   std::unique_ptr<ir::Module> module =
       SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text);
-  opt::analysis::TypeManager manager(*module);
+  opt::analysis::TypeManager manager(IgnoreMessage, *module);
 
   EXPECT_EQ(type_id_strs.size(), manager.NumTypes());
   EXPECT_EQ(2u, manager.NumForwardPointers());
@@ -120,7 +120,7 @@
   )";
   std::unique_ptr<ir::Module> module =
       SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text);
-  opt::analysis::TypeManager manager(*module);
+  opt::analysis::TypeManager manager(IgnoreMessage, *module);
 
   ASSERT_EQ(7u, manager.NumTypes());
   ASSERT_EQ(0u, manager.NumForwardPointers());
@@ -170,7 +170,7 @@
   )";
   std::unique_ptr<ir::Module> module =
       SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text);
-  opt::analysis::TypeManager manager(*module);
+  opt::analysis::TypeManager manager(IgnoreMessage, *module);
 
   ASSERT_EQ(10u, manager.NumTypes());
   ASSERT_EQ(0u, manager.NumForwardPointers());
@@ -208,7 +208,7 @@
   )";
   std::unique_ptr<ir::Module> module =
       SpvTools(SPV_ENV_UNIVERSAL_1_1).BuildModule(text);
-  opt::analysis::TypeManager manager(*module);
+  opt::analysis::TypeManager manager(IgnoreMessage, *module);
 
   ASSERT_EQ(5u, manager.NumTypes());
   ASSERT_EQ(0u, manager.NumForwardPointers());
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 0e3be79..d5784e1 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -16,6 +16,7 @@
 #include <iostream>
 #include <vector>
 
+#include "message.h"
 #include "source/opt/ir_loader.h"
 #include "source/opt/libspirv.hpp"
 #include "source/opt/pass_manager.h"
@@ -62,7 +63,11 @@
 
   spv_target_env target_env = SPV_ENV_UNIVERSAL_1_1;
 
-  opt::PassManager pass_manager;
+  opt::PassManager pass_manager([](MessageLevel level, const char* source,
+                                   const spv_position_t& position,
+                                   const char* message) {
+    std::cerr << StringifyMessage(level, source, position, message);
+  });
 
   for (int argi = 1; argi < argc; ++argi) {
     const char* cur_arg = argv[argi];