Replace all uses of assert() with TINT_ICE macros

Use TINT_ICE() where we have diagnostics, TINT_ASSERT() where we do not.

Change-Id: Ic6e842a7afdd957654c3461e5d03ecec7332e6f9
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/46444
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/ast/module.cc b/src/ast/module.cc
index 41e3629..97651c3 100644
--- a/src/ast/module.cc
+++ b/src/ast/module.cc
@@ -55,7 +55,10 @@
 
 void Module::Copy(CloneContext* ctx, const Module* src) {
   for (auto* decl : ctx->Clone(src->global_declarations_)) {
-    assert(decl);
+    if (!decl) {
+      TINT_ICE(ctx->dst->Diagnostics()) << "src global declaration was nullptr";
+      continue;
+    }
     if (auto* ty = decl->As<type::Type>()) {
       AddConstructedType(ty);
     } else if (auto* func = decl->As<Function>()) {
diff --git a/src/inspector/inspector.cc b/src/inspector/inspector.cc
index 5df2847..17aa165 100644
--- a/src/inspector/inspector.cc
+++ b/src/inspector/inspector.cc
@@ -44,7 +44,7 @@
 
 void AppendResourceBindings(std::vector<ResourceBinding>* dest,
                             const std::vector<ResourceBinding>& orig) {
-  assert(dest);
+  TINT_ASSERT(dest);
   if (!dest) {
     return;
   }
diff --git a/src/intrinsic_table.cc b/src/intrinsic_table.cc
index 6728365..393f05b 100644
--- a/src/intrinsic_table.cc
+++ b/src/intrinsic_table.cc
@@ -1386,7 +1386,11 @@
   // This stage also populates the open_types and open_numbers.
   auto count = std::min(parameters.size(), args.size());
   for (size_t i = 0; i < count; i++) {
-    assert(args[i]);
+    if (!args[i]) {
+      TINT_ICE(diagnostics) << "args[" << i << "] is nullptr";
+      return nullptr;
+    }
+
     auto* arg_ty = args[i];
     if (auto* ptr = arg_ty->As<type::Pointer>()) {
       if (!parameters[i].matcher->ExpectsPointer()) {
@@ -1447,7 +1451,10 @@
   Builder::BuildState builder_state{builder.Types(), matcher_state.open_types,
                                     matcher_state.open_numbers};
   auto* ret = return_type->Build(builder_state);
-  assert(ret);  // Build() must return a type
+  if (!ret) {
+    TINT_ICE(diagnostics) << "Build() did not return a type";
+    return nullptr;
+  }
 
   // Build the semantic parameters
   semantic::ParameterList params;
diff --git a/src/program.cc b/src/program.cc
index 3fbf4c9..6f98099 100644
--- a/src/program.cc
+++ b/src/program.cc
@@ -119,7 +119,7 @@
 }
 
 void Program::AssertNotMoved() const {
-  assert(!moved_);
+  TINT_ASSERT(!moved_);
 }
 
 }  // namespace tint
diff --git a/src/program_builder.cc b/src/program_builder.cc
index ac01248..1379730 100644
--- a/src/program_builder.cc
+++ b/src/program_builder.cc
@@ -17,6 +17,7 @@
 #include "src/ast/assignment_statement.h"
 #include "src/ast/call_statement.h"
 #include "src/ast/variable_decl_statement.h"
+#include "src/debug.h"
 #include "src/demangler.h"
 #include "src/semantic/expression.h"
 
@@ -74,7 +75,10 @@
 }
 
 void ProgramBuilder::AssertNotMoved() const {
-  assert(!moved_);
+  if (moved_) {
+    TINT_ICE(const_cast<ProgramBuilder*>(this)->Diagnostics())
+        << "Attempting to use ProgramBuilder after it has been moved";
+  }
 }
 
 type::Type* ProgramBuilder::TypeOf(ast::Expression* expr) const {
diff --git a/src/reader/spirv/function.cc b/src/reader/spirv/function.cc
index a7e3664..5139f9f 100644
--- a/src/reader/spirv/function.cc
+++ b/src/reader/spirv/function.cc
@@ -734,7 +734,7 @@
 FunctionEmitter::StatementBlock::~StatementBlock() = default;
 
 void FunctionEmitter::StatementBlock::Finalize(ProgramBuilder* pb) {
-  assert(!finalized_ /* Finalize() must only be called once */);
+  TINT_ASSERT(!finalized_ /* Finalize() must only be called once */);
 
   for (size_t i = 0; i < statements_.size(); i++) {
     if (auto* sb = statements_[i]->As<StatementBuilder>()) {
@@ -750,7 +750,7 @@
 }
 
 void FunctionEmitter::StatementBlock::Add(ast::Statement* statement) {
-  assert(!finalized_ /* Add() must not be called after Finalize() */);
+  TINT_ASSERT(!finalized_ /* Add() must not be called after Finalize() */);
   statements_.emplace_back(statement);
 }
 
@@ -762,8 +762,8 @@
 
 void FunctionEmitter::PushGuard(const std::string& guard_name,
                                 uint32_t end_id) {
-  assert(!statements_stack_.empty());
-  assert(!guard_name.empty());
+  TINT_ASSERT(!statements_stack_.empty());
+  TINT_ASSERT(!guard_name.empty());
   // Guard control flow by the guard variable.  Introduce a new
   // if-selection with a then-clause ending at the same block
   // as the statement block at the top of the stack.
@@ -780,7 +780,7 @@
 }
 
 void FunctionEmitter::PushTrueGuard(uint32_t end_id) {
-  assert(!statements_stack_.empty());
+  TINT_ASSERT(!statements_stack_.empty());
   const auto& top = statements_stack_.back();
 
   auto* cond = MakeTrue(Source{});
@@ -793,14 +793,14 @@
 }
 
 const ast::StatementList FunctionEmitter::ast_body() {
-  assert(!statements_stack_.empty());
+  TINT_ASSERT(!statements_stack_.empty());
   auto& entry = statements_stack_[0];
   entry.Finalize(&builder_);
   return entry.GetStatements();
 }
 
 ast::Statement* FunctionEmitter::AddStatement(ast::Statement* statement) {
-  assert(!statements_stack_.empty());
+  TINT_ASSERT(!statements_stack_.empty());
   if (statement != nullptr) {
     statements_stack_.back().Add(statement);
   }
@@ -808,9 +808,9 @@
 }
 
 ast::Statement* FunctionEmitter::LastStatement() {
-  assert(!statements_stack_.empty());
+  TINT_ASSERT(!statements_stack_.empty());
   auto& statement_list = statements_stack_.back().GetStatements();
-  assert(!statement_list.empty());
+  TINT_ASSERT(!statement_list.empty());
   return statement_list.back();
 }
 
@@ -1221,7 +1221,7 @@
   //      block. Also mark the the most recent continue target for which we
   //      haven't reached the backedge block.
 
-  assert(block_order_.size() > 0);
+  TINT_ASSERT(block_order_.size() > 0);
   constructs_.clear();
   const auto entry_id = block_order_[0];
 
@@ -1242,8 +1242,8 @@
     // A loop construct is added right after its associated continue construct.
     // In that case, adjust the parent up.
     if (k == Construct::kLoop) {
-      assert(parent);
-      assert(parent->kind == Construct::kContinue);
+      TINT_ASSERT(parent);
+      TINT_ASSERT(parent->kind == Construct::kContinue);
       scope_end_pos = parent->end_pos;
       parent = parent->parent;
     }
@@ -1262,9 +1262,9 @@
 
   for (uint32_t i = 0; i < block_order_.size(); ++i) {
     const auto block_id = block_order_[i];
-    assert(block_id > 0);
+    TINT_ASSERT(block_id > 0);
     auto* block_info = GetBlockInfo(block_id);
-    assert(block_info);
+    TINT_ASSERT(block_info);
 
     if (enclosing.empty()) {
       return Fail() << "internal error: too many merge blocks before block "
@@ -1321,7 +1321,7 @@
       }
     }
 
-    assert(top);
+    TINT_ASSERT(top);
     block_info->construct = top;
   }
 
@@ -1530,9 +1530,9 @@
   //    NEC(S) is the parent of NEC(T).
 
   for (const auto src : block_order_) {
-    assert(src > 0);
+    TINT_ASSERT(src > 0);
     auto* src_info = GetBlockInfo(src);
-    assert(src_info);
+    TINT_ASSERT(src_info);
     const auto src_pos = src_info->pos;
     const auto& src_construct = *(src_info->construct);
 
@@ -1570,7 +1570,7 @@
     for (const auto dest : successors) {
       const auto* dest_info = GetBlockInfo(dest);
       // We've already checked terminators are valid.
-      assert(dest_info);
+      TINT_ASSERT(dest_info);
       const auto dest_pos = dest_info->pos;
 
       // Insert the edge kind entry and keep a handle to update
@@ -1595,7 +1595,7 @@
                         << " (violates post-dominance rule)";
         }
         const auto* ct_info = GetBlockInfo(continue_construct->begin_id);
-        assert(ct_info);
+        TINT_ASSERT(ct_info);
         if (ct_info->header_for_continue != dest) {
           return Fail()
                  << "Invalid backedge (" << src << "->" << dest
@@ -1874,7 +1874,7 @@
       // The first clause might be a then-clause or an else-clause.
       const auto second_head = std::max(true_head_pos, false_head_pos);
       const auto end_first_clause_pos = second_head - 1;
-      assert(end_first_clause_pos < block_order_.size());
+      TINT_ASSERT(end_first_clause_pos < block_order_.size());
       const auto end_first_clause = block_order_[end_first_clause_pos];
       uint32_t premerge_id = 0;
       uint32_t if_break_id = 0;
@@ -2072,15 +2072,15 @@
 
   // Upon entry, the statement stack has one entry representing the whole
   // function.
-  assert(!constructs_.empty());
+  TINT_ASSERT(!constructs_.empty());
   Construct* function_construct = constructs_[0].get();
-  assert(function_construct != nullptr);
-  assert(function_construct->kind == Construct::kFunction);
+  TINT_ASSERT(function_construct != nullptr);
+  TINT_ASSERT(function_construct->kind == Construct::kFunction);
   // Make the first entry valid by filling in the construct field, which
   // had not been computed at the time the entry was first created.
   // TODO(dneto): refactor how the first construct is created vs.
   // this statements stack entry is populated.
-  assert(statements_stack_.size() == 1);
+  TINT_ASSERT(statements_stack_.size() == 1);
   statements_stack_[0].SetConstruct(function_construct);
 
   for (auto block_id : block_order()) {
@@ -2270,8 +2270,8 @@
 bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) {
   // The block is the if-header block.  So its construct is the if construct.
   auto* construct = block_info.construct;
-  assert(construct->kind == Construct::kIfSelection);
-  assert(construct->begin_id == block_info.id);
+  TINT_ASSERT(construct->kind == Construct::kIfSelection);
+  TINT_ASSERT(construct->begin_id == block_info.id);
 
   const uint32_t true_head = block_info.true_head;
   const uint32_t false_head = block_info.false_head;
@@ -2396,8 +2396,8 @@
 bool FunctionEmitter::EmitSwitchStart(const BlockInfo& block_info) {
   // The block is the if-header block.  So its construct is the if construct.
   auto* construct = block_info.construct;
-  assert(construct->kind == Construct::kSwitchSelection);
-  assert(construct->begin_id == block_info.id);
+  TINT_ASSERT(construct->kind == Construct::kSwitchSelection);
+  TINT_ASSERT(construct->begin_id == block_info.id);
   const auto* branch = block_info.basic_block->terminator();
 
   const auto selector_id = branch->GetSingleWordInOperand(0);
@@ -2443,7 +2443,7 @@
       clause_heads[w] = clause_heads[r];
     }
     // We know it's not empty because it always has at least a default clause.
-    assert(!clause_heads.empty());
+    TINT_ASSERT(!clause_heads.empty());
     clause_heads.resize(w + 1);
   }
 
@@ -2652,9 +2652,9 @@
       // Unless forced, don't bother with a break at the end of a case/default
       // clause.
       const auto header = dest_info.header_for_merge;
-      assert(header != 0);
+      TINT_ASSERT(header != 0);
       const auto* exiting_construct = GetBlockInfo(header)->construct;
-      assert(exiting_construct->kind == Construct::kSwitchSelection);
+      TINT_ASSERT(exiting_construct->kind == Construct::kSwitchSelection);
       const auto candidate_next_case_pos = src_info.pos + 1;
       // Leaving the last block from the last case?
       if (candidate_next_case_pos == dest_info.pos) {
@@ -2798,7 +2798,7 @@
   // Emit declarations of hoisted variables, in index order.
   for (auto id : sorted_by_index(block_info.hoisted_ids)) {
     const auto* def_inst = def_use_mgr_->GetDef(id);
-    assert(def_inst);
+    TINT_ASSERT(def_inst);
     auto* ast_type =
         RemapStorageClass(parser_impl_.ConvertType(def_inst->type_id()), id);
     AddStatement(create<ast::VariableDeclStatement>(
@@ -2811,9 +2811,9 @@
   // Emit declarations of phi state variables, in index order.
   for (auto id : sorted_by_index(block_info.phis_needing_state_vars)) {
     const auto* def_inst = def_use_mgr_->GetDef(id);
-    assert(def_inst);
+    TINT_ASSERT(def_inst);
     const auto phi_var_name = GetDefInfo(id)->phi_var;
-    assert(!phi_var_name.empty());
+    TINT_ASSERT(!phi_var_name.empty());
     auto* var = create<ast::Variable>(
         Source{},                                       // source
         builder_.Symbols().Register(phi_var_name),      // symbol
@@ -3066,7 +3066,7 @@
       }
       auto expr = MakeExpression(ptr_id);
       // The load result type is the pointee type of its operand.
-      assert(expr.type->Is<type::Pointer>());
+      TINT_ASSERT(expr.type->Is<type::Pointer>());
       expr.type = expr.type->As<type::Pointer>()->type();
       return EmitConstDefOrWriteToHoistedVar(inst, expr);
     }
@@ -3550,8 +3550,8 @@
     const auto pointer_type_id =
         type_mgr_->FindPointerToType(pointee_type_id, storage_class);
     auto* ast_pointer_type = parser_impl_.ConvertType(pointer_type_id);
-    assert(ast_pointer_type);
-    assert(ast_pointer_type->Is<type::Pointer>());
+    TINT_ASSERT(ast_pointer_type);
+    TINT_ASSERT(ast_pointer_type->Is<type::Pointer>());
     current_expr = TypedExpression{ast_pointer_type, next_expr};
   }
   return current_expr;
@@ -3746,7 +3746,7 @@
           source, MakeExpression(vec0_id).expr, Swizzle(index)));
     } else if (index < vec0_len + vec1_len) {
       const auto sub_index = index - vec0_len;
-      assert(sub_index < kMaxVectorLen);
+      TINT_ASSERT(sub_index < kMaxVectorLen);
       values.emplace_back(create<ast::MemberAccessorExpression>(
           source, MakeExpression(vec1_id).expr, Swizzle(sub_index)));
     } else if (index == 0xFFFFFFFF) {
@@ -4045,7 +4045,7 @@
                                                     uint32_t last_pos) const {
   const auto* enclosing_construct =
       GetBlockInfo(block_order_[first_pos])->construct;
-  assert(enclosing_construct != nullptr);
+  TINT_ASSERT(enclosing_construct != nullptr);
   // Constructs are strictly nesting, so follow parent pointers
   while (enclosing_construct &&
          !enclosing_construct->ScopeContainsPos(last_pos)) {
@@ -4057,7 +4057,7 @@
         sibling_loop ? sibling_loop : enclosing_construct->parent;
   }
   // At worst, we go all the way out to the function construct.
-  assert(enclosing_construct != nullptr);
+  TINT_ASSERT(enclosing_construct != nullptr);
   return enclosing_construct;
 }
 
diff --git a/src/reader/spirv/function.h b/src/reader/spirv/function.h
index 052f056..228df47 100644
--- a/src/reader/spirv/function.h
+++ b/src/reader/spirv/function.h
@@ -998,7 +998,7 @@
   /// @return the built StatementBuilder
   template <typename T, typename... ARGS>
   T* AddStatementBuilder(ARGS&&... args) {
-    assert(!statements_stack_.empty());
+    TINT_ASSERT(!statements_stack_.empty());
     return statements_stack_.back().AddStatementBuilder<T>(
         std::forward<ARGS>(args)...);
   }
diff --git a/src/reader/wgsl/parser_impl.cc b/src/reader/wgsl/parser_impl.cc
index 53036cb..25094bf 100644
--- a/src/reader/wgsl/parser_impl.cc
+++ b/src/reader/wgsl/parser_impl.cc
@@ -233,7 +233,6 @@
 
 void ParserImpl::register_constructed(const std::string& name,
                                       type::Type* type) {
-  assert(type);
   registered_constructs_[name] = type;
 }
 
@@ -3156,7 +3155,9 @@
   auto result = body();
   --sync_depth_;
 
-  assert(sync_tokens_.back() == tok);
+  if (sync_tokens_.back() != tok) {
+    TINT_ICE(builder_.Diagnostics()) << "sync_tokens is out of sync";
+  }
   sync_tokens_.pop_back();
 
   if (result.errored) {
diff --git a/src/reader/wgsl/parser_impl.h b/src/reader/wgsl/parser_impl.h
index 080dda5..c048a3e 100644
--- a/src/reader/wgsl/parser_impl.h
+++ b/src/reader/wgsl/parser_impl.h
@@ -109,7 +109,7 @@
     /// return type will always be a pointer to a non-pointer type. #errored
     /// must be false to call.
     inline typename detail::OperatorArrow<T>::type operator->() {
-      assert(!errored);
+      TINT_ASSERT(!errored);
       return detail::OperatorArrow<T>::ptr(value);
     }
 
@@ -180,7 +180,7 @@
     /// return type will always be a pointer to a non-pointer type. #errored
     /// must be false to call.
     inline typename detail::OperatorArrow<T>::type operator->() {
-      assert(!errored);
+      TINT_ASSERT(!errored);
       return detail::OperatorArrow<T>::ptr(value);
     }
 
diff --git a/src/resolver/resolver.cc b/src/resolver/resolver.cc
index a6ef97e..f1a5338 100644
--- a/src/resolver/resolver.cc
+++ b/src/resolver/resolver.cc
@@ -1329,7 +1329,10 @@
 }
 
 void Resolver::SetType(ast::Expression* expr, type::Type* type) {
-  assert(expr_info_.count(expr) == 0);
+  if (expr_info_.count(expr)) {
+    TINT_ICE(builder_->Diagnostics())
+        << "SetType() called twice for the same expression";
+  }
   expr_info_.emplace(expr, ExpressionInfo{type, current_statement_});
 }
 
diff --git a/src/semantic/info.h b/src/semantic/info.h
index ec14cee..b058ff3 100644
--- a/src/semantic/info.h
+++ b/src/semantic/info.h
@@ -15,10 +15,9 @@
 #ifndef SRC_SEMANTIC_INFO_H_
 #define SRC_SEMANTIC_INFO_H_
 
-#include <assert.h>
-
 #include <unordered_map>
 
+#include "src/debug.h"
 #include "src/semantic/node.h"
 #include "src/semantic/type_mappings.h"
 
@@ -63,7 +62,7 @@
   void Add(const AST_OR_TYPE* node,
            const SemanticNodeTypeFor<AST_OR_TYPE>* sem_node) {
     // Check there's no semantic info already existing for the node
-    assert(Get(node) == nullptr);
+    TINT_ASSERT(Get(node) == nullptr);
     map.emplace(node, sem_node);
   }
 
diff --git a/src/type/access_control_type.cc b/src/type/access_control_type.cc
index 4ecefe3..0d1cbf5 100644
--- a/src/type/access_control_type.cc
+++ b/src/type/access_control_type.cc
@@ -23,8 +23,8 @@
 
 AccessControl::AccessControl(ast::AccessControl access, Type* subtype)
     : access_(access), subtype_(subtype) {
-  assert(subtype_);
-  assert(!subtype_->Is<AccessControl>());
+  TINT_ASSERT(subtype_);
+  TINT_ASSERT(!subtype_->Is<AccessControl>());
 }
 
 AccessControl::AccessControl(AccessControl&&) = default;
diff --git a/src/type/alias_type.cc b/src/type/alias_type.cc
index b155cc2..d0ec9c0 100644
--- a/src/type/alias_type.cc
+++ b/src/type/alias_type.cc
@@ -23,7 +23,7 @@
 
 Alias::Alias(const Symbol& sym, Type* subtype)
     : symbol_(sym), subtype_(subtype) {
-  assert(subtype_);
+  TINT_ASSERT(subtype_);
 }
 
 Alias::Alias(Alias&&) = default;
diff --git a/src/type/array_type.cc b/src/type/array_type.cc
index 46fe3ed..d84c36b 100644
--- a/src/type/array_type.cc
+++ b/src/type/array_type.cc
@@ -31,7 +31,7 @@
 Array::~Array() = default;
 
 std::string Array::type_name() const {
-  assert(subtype_);
+  TINT_ASSERT(subtype_);
 
   std::string type_name = "__array" + subtype_->type_name();
   if (!IsRuntimeArray()) {
diff --git a/src/type/depth_texture_type.cc b/src/type/depth_texture_type.cc
index e8dc4ff..2dbd64c 100644
--- a/src/type/depth_texture_type.cc
+++ b/src/type/depth_texture_type.cc
@@ -22,19 +22,15 @@
 namespace type {
 namespace {
 
-#ifndef NDEBUG
-
 bool IsValidDepthDimension(TextureDimension dim) {
   return dim == TextureDimension::k2d || dim == TextureDimension::k2dArray ||
          dim == TextureDimension::kCube || dim == TextureDimension::kCubeArray;
 }
 
-#endif  // NDEBUG
-
 }  // namespace
 
 DepthTexture::DepthTexture(TextureDimension dim) : Base(dim) {
-  assert(IsValidDepthDimension(dim));
+  TINT_ASSERT(IsValidDepthDimension(dim));
 }
 
 DepthTexture::DepthTexture(DepthTexture&&) = default;
diff --git a/src/type/matrix_type.cc b/src/type/matrix_type.cc
index 02e225b..c840d0f 100644
--- a/src/type/matrix_type.cc
+++ b/src/type/matrix_type.cc
@@ -23,10 +23,10 @@
 
 Matrix::Matrix(Type* subtype, uint32_t rows, uint32_t columns)
     : subtype_(subtype), rows_(rows), columns_(columns) {
-  assert(rows > 1);
-  assert(rows < 5);
-  assert(columns > 1);
-  assert(columns < 5);
+  TINT_ASSERT(rows > 1);
+  TINT_ASSERT(rows < 5);
+  TINT_ASSERT(columns > 1);
+  TINT_ASSERT(columns < 5);
 }
 
 Matrix::Matrix(Matrix&&) = default;
diff --git a/src/type/multisampled_texture_type.cc b/src/type/multisampled_texture_type.cc
index 7037b91..e55ebd5 100644
--- a/src/type/multisampled_texture_type.cc
+++ b/src/type/multisampled_texture_type.cc
@@ -23,7 +23,7 @@
 
 MultisampledTexture::MultisampledTexture(TextureDimension dim, Type* type)
     : Base(dim), type_(type) {
-  assert(type_);
+  TINT_ASSERT(type_);
 }
 
 MultisampledTexture::MultisampledTexture(MultisampledTexture&&) = default;
diff --git a/src/type/sampled_texture_type.cc b/src/type/sampled_texture_type.cc
index 50649ac..1e3d636 100644
--- a/src/type/sampled_texture_type.cc
+++ b/src/type/sampled_texture_type.cc
@@ -23,7 +23,7 @@
 
 SampledTexture::SampledTexture(TextureDimension dim, Type* type)
     : Base(dim), type_(type) {
-  assert(type_);
+  TINT_ASSERT(type_);
 }
 
 SampledTexture::SampledTexture(SampledTexture&&) = default;
diff --git a/src/type/vector_type.cc b/src/type/vector_type.cc
index ab12790..ce65744 100644
--- a/src/type/vector_type.cc
+++ b/src/type/vector_type.cc
@@ -22,8 +22,8 @@
 namespace type {
 
 Vector::Vector(Type* subtype, uint32_t size) : subtype_(subtype), size_(size) {
-  assert(size_ > 1);
-  assert(size_ < 5);
+  TINT_ASSERT(size_ > 1);
+  TINT_ASSERT(size_ < 5);
 }
 
 Vector::Vector(Vector&&) = default;
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 705a3d4..40916db 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -776,7 +776,10 @@
   };
 
   auto* texture = arg(Usage::kTexture);
-  assert(texture);
+  if (!texture) {
+    TINT_ICE(diagnostics_) << "missing texture argument";
+    return false;
+  }
 
   auto* texture_type = TypeOf(texture)->UnwrapAll()->As<type::Texture>();
 
@@ -934,8 +937,10 @@
         pre << dims;
       } else {
         static constexpr char xyzw[] = {'x', 'y', 'z', 'w'};
-        assert(num_dimensions > 0);
-        assert(num_dimensions <= 4);
+        if (num_dimensions < 0 || num_dimensions > 4) {
+          TINT_ICE(diagnostics_) << "vector dimensions are " << num_dimensions;
+          return false;
+        }
         for (int i = 0; i < num_dimensions; i++) {
           if (i > 0) {
             pre << ", ";
@@ -999,7 +1004,10 @@
   }
 
   auto* param_coords = arg(Usage::kCoords);
-  assert(param_coords);
+  if (!param_coords) {
+    TINT_ICE(diagnostics_) << "missing coords argument";
+    return false;
+  }
 
   auto emit_vector_appended_with_i32_zero = [&](tint::ast::Expression* vector) {
     auto* i32 = builder_.create<type::I32>();
diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc
index 52152df..d7e47e1 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -474,7 +474,10 @@
   };
 
   auto* texture = arg(Usage::kTexture);
-  assert(texture);
+  if (!texture) {
+    TINT_ICE(diagnostics_) << "missing texture arg";
+    return false;
+  }
 
   auto* texture_type = TypeOf(texture)->UnwrapAll()->As<type::Texture>();
 
diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc
index 96b5f65..ca555ec 100644
--- a/src/writer/spirv/builder.cc
+++ b/src/writer/spirv/builder.cc
@@ -1007,8 +1007,11 @@
 }
 
 uint32_t Builder::GenerateAccessorExpression(ast::Expression* expr) {
-  assert(expr->Is<ast::ArrayAccessorExpression>() ||
-         expr->Is<ast::MemberAccessorExpression>());
+  if (!expr->IsAnyOf<ast::ArrayAccessorExpression,
+                     ast::MemberAccessorExpression>()) {
+    TINT_ICE(builder_.Diagnostics()) << "expression is not an accessor";
+    return 0;
+  }
 
   // Gather a list of all the member and array accessors that are in this chain.
   // The list is built in reverse order as that's the order we need to access
@@ -2113,12 +2116,17 @@
   // Generates the argument with the given usage, returning the operand ID
   auto gen_arg = [&](Usage usage) {
     auto* argument = arg(usage);
-    assert(argument);
+    if (!argument) {
+      TINT_ICE(builder_.Diagnostics())
+          << "missing argument " << static_cast<int>(usage);
+    }
     return gen(argument);
   };
 
   auto* texture = arg(Usage::kTexture);
-  assert(texture);
+  if (!texture) {
+    TINT_ICE(builder_.Diagnostics()) << "missing texture argument";
+  }
 
   auto* texture_type = TypeOf(texture)->UnwrapAll()->As<type::Texture>();
 
@@ -3007,8 +3015,7 @@
     dim_literal = SpvDim1D;
     if (texture->Is<type::SampledTexture>()) {
       push_capability(SpvCapabilitySampled1D);
-    } else {
-      assert(texture->Is<type::StorageTexture>());
+    } else if (texture->Is<type::StorageTexture>()) {
       push_capability(SpvCapabilityImage1D);
     }
   }
diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h
index c99749f..f254dc6 100644
--- a/src/writer/spirv/builder.h
+++ b/src/writer/spirv/builder.h
@@ -191,7 +191,10 @@
   /// Pushes a variable to the current function
   /// @param operands the variable operands
   void push_function_var(const OperandList& operands) {
-    assert(!functions_.empty());
+    if (functions_.empty()) {
+      TINT_ICE(builder_.Diagnostics())
+          << "push_function_var() called without a function";
+    }
     functions_.back().push_var(operands);
   }