Implement discard semantics

Implement new transform UnwindDiscardFunctions that replaces discard
statements with setting a module-level bool, adds a check and return for
this bool after every function call that may discard, and finally
invokes a single function that executes a discard from top-level
functions.

Regenerated tests and remove HLSL ones that used to fail FXC because it
had difficulty with discard.

Bug: tint:1478
Bug: chromium:1118
Change-Id: I09d680f59e2d5d0cad907bfbbdd426aae76d4bf3
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/84221
Reviewed-by: James Price <jrprice@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 91bee00..1646c85 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -487,6 +487,8 @@
     "transform/transform.h",
     "transform/unshadow.cc",
     "transform/unshadow.h",
+    "transform/unwind_discard_functions.cc",
+    "transform/unwind_discard_functions.h",
     "transform/utils/hoist_to_decl_before.cc",
     "transform/utils/hoist_to_decl_before.h",
     "transform/var_for_dynamic_index.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 5e077e6..1f5e90c 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -365,6 +365,8 @@
   transform/transform.h
   transform/unshadow.cc
   transform/unshadow.h
+  transform/unwind_discard_functions.cc
+  transform/unwind_discard_functions.h
   transform/vectorize_scalar_matrix_constructors.cc
   transform/vectorize_scalar_matrix_constructors.h
   transform/var_for_dynamic_index.cc
@@ -1031,6 +1033,7 @@
       transform/single_entry_point_test.cc
       transform/test_helper.h
       transform/unshadow_test.cc
+      transform/unwind_discard_functions_test.cc
       transform/var_for_dynamic_index_test.cc
       transform/vectorize_scalar_matrix_constructors_test.cc
       transform/vertex_pulling_test.cc
diff --git a/src/tint/sem/if_statement.cc b/src/tint/sem/if_statement.cc
index d5f8fb3..38a7395 100644
--- a/src/tint/sem/if_statement.cc
+++ b/src/tint/sem/if_statement.cc
@@ -40,5 +40,9 @@
 
 ElseStatement::~ElseStatement() = default;
 
+const ast::ElseStatement* ElseStatement::Declaration() const {
+  return static_cast<const ast::ElseStatement*>(Base::Declaration());
+}
+
 }  // namespace sem
 }  // namespace tint
diff --git a/src/tint/sem/if_statement.h b/src/tint/sem/if_statement.h
index 7dc2a11..c38a32a 100644
--- a/src/tint/sem/if_statement.h
+++ b/src/tint/sem/if_statement.h
@@ -73,6 +73,9 @@
   /// Destructor
   ~ElseStatement() override;
 
+  /// @returns the AST node
+  const ast::ElseStatement* Declaration() const;
+
   /// @returns the else-statement condition expression
   const Expression* Condition() const { return condition_; }
 
diff --git a/src/tint/transform/glsl.cc b/src/tint/transform/glsl.cc
index c7352c4..a1cb69c 100644
--- a/src/tint/transform/glsl.cc
+++ b/src/tint/transform/glsl.cc
@@ -34,6 +34,7 @@
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/transform/single_entry_point.h"
 #include "src/tint/transform/unshadow.h"
+#include "src/tint/transform/unwind_discard_functions.h"
 #include "src/tint/transform/zero_init_workgroup_memory.h"
 
 TINT_INSTANTIATE_TYPEINFO(tint::transform::Glsl);
@@ -84,6 +85,7 @@
   }
   manager.Add<CanonicalizeEntryPointIO>();
   manager.Add<PromoteSideEffectsToDecl>();
+  manager.Add<UnwindDiscardFunctions>();
   manager.Add<SimplifyPointers>();
 
   manager.Add<RemovePhonies>();
diff --git a/src/tint/transform/test_helper.h b/src/tint/transform/test_helper.h
index 974cba0..8a36df1 100644
--- a/src/tint/transform/test_helper.h
+++ b/src/tint/transform/test_helper.h
@@ -118,7 +118,8 @@
   template <typename TRANSFORM>
   bool ShouldRun(Program&& program, const DataMap& data = {}) {
     EXPECT_TRUE(program.IsValid()) << program.Diagnostics().str();
-    return TRANSFORM().ShouldRun(&program, data);
+    const Transform& t = TRANSFORM();
+    return t.ShouldRun(&program, data);
   }
 
   /// @param in the input WGSL source
diff --git a/src/tint/transform/unwind_discard_functions.cc b/src/tint/transform/unwind_discard_functions.cc
new file mode 100644
index 0000000..80a47b1
--- /dev/null
+++ b/src/tint/transform/unwind_discard_functions.cc
@@ -0,0 +1,432 @@
+// Copyright 2022 The Tint Authors.
+//
+// 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 "src/tint/transform/unwind_discard_functions.h"
+
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "src/tint/ast/discard_statement.h"
+#include "src/tint/ast/return_statement.h"
+#include "src/tint/ast/traverse_expressions.h"
+#include "src/tint/sem/block_statement.h"
+#include "src/tint/sem/call.h"
+#include "src/tint/sem/for_loop_statement.h"
+#include "src/tint/sem/function.h"
+#include "src/tint/sem/if_statement.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::transform::UnwindDiscardFunctions);
+
+namespace tint::transform {
+namespace {
+
+class State {
+ private:
+  CloneContext& ctx;
+  ProgramBuilder& b;
+  const sem::Info& sem;
+  Symbol module_discard_var_name;   // Use ModuleDiscardVarName() to read
+  Symbol module_discard_func_name;  // Use ModuleDiscardFuncName() to read
+
+  // For the input statement, returns the block and statement within that
+  // block to insert before/after.
+  std::pair<const sem::BlockStatement*, const ast::Statement*>
+  GetInsertionPoint(const ast::Statement* stmt) {
+    using RetType =
+        std::pair<const sem::BlockStatement*, const ast::Statement*>;
+
+    if (auto* sem_stmt = sem.Get(stmt)) {
+      auto* parent = sem_stmt->Parent();
+      return Switch(
+          parent,
+          [&](const sem::BlockStatement* block) -> RetType {
+            // Common case, just insert in the current block above the input
+            // statement.
+            return {block, stmt};
+          },
+          [&](const sem::ForLoopStatement* fl) -> RetType {
+            // `stmt` is either the for loop initializer or the continuing
+            // statement of a for-loop.
+            if (fl->Declaration()->initializer == stmt) {
+              // For loop init, insert above the for loop itself.
+              return {fl->Block(), fl->Declaration()};
+            }
+
+            TINT_ICE(Transform, b.Diagnostics())
+                << "cannot insert before or after continuing statement of a "
+                   "for-loop";
+            return {};
+          },
+          [&](Default) -> RetType {
+            TINT_ICE(Transform, b.Diagnostics())
+                << "expected parent of statement to be either a block or for "
+                   "loop";
+            return {};
+          });
+    }
+
+    return {};
+  }
+
+  // If `block`'s parent is of type TO, returns pointer to it.
+  template <typename TO>
+  const TO* ParentAs(const ast::BlockStatement* block) {
+    if (auto* sem_block = sem.Get(block)) {
+      return As<TO>(sem_block->Parent());
+    }
+    return nullptr;
+  }
+
+  // Returns true if `sem_expr` contains a call expression that may
+  // (transitively) execute a discard statement.
+  bool MayDiscard(const sem::Expression* sem_expr) {
+    return sem_expr && sem_expr->Behaviors().Contains(sem::Behavior::kDiscard);
+  }
+
+  // Lazily creates and returns the name of the module bool variable for whether
+  // to discard: "tint_discard".
+  Symbol ModuleDiscardVarName() {
+    if (!module_discard_var_name.IsValid()) {
+      module_discard_var_name = b.Symbols().New("tint_discard");
+      ctx.dst->Global(module_discard_var_name, b.ty.bool_(), b.Expr(false),
+                      ast::StorageClass::kPrivate);
+    }
+    return module_discard_var_name;
+  }
+
+  // Lazily creates and returns the name of the function that contains a single
+  // discard statement: "tint_discard_func".
+  // We do this to avoid having multiple discard statements in a single program,
+  // which causes problems in certain backends (see crbug.com/1118).
+  Symbol ModuleDiscardFuncName() {
+    if (!module_discard_func_name.IsValid()) {
+      module_discard_func_name = b.Symbols().New("tint_discard_func");
+      b.Func(module_discard_func_name, {}, b.ty.void_(), {b.Discard()});
+    }
+    return module_discard_func_name;
+  }
+
+  // Creates "return <default return value>;" based on the return type of
+  // `stmt`'s owning function.
+  const ast::ReturnStatement* Return(const ast::Statement* stmt) {
+    const ast::Expression* ret_val = nullptr;
+    auto* ret_type = sem.Get(stmt)->Function()->Declaration()->return_type;
+    if (!ret_type->Is<ast::Void>()) {
+      ret_val = b.Construct(ctx.Clone(ret_type));
+    }
+    return b.Return(ret_val);
+  }
+
+  // Returns true if the function `stmt` is in is an entry point
+  bool IsInEntryPointFunc(const ast::Statement* stmt) {
+    return sem.Get(stmt)->Function()->Declaration()->IsEntryPoint();
+  }
+
+  // Creates "tint_discard_func();"
+  const ast::CallStatement* CallDiscardFunc() {
+    auto func_name = ModuleDiscardFuncName();
+    return b.CallStmt(b.Call(func_name));
+  }
+
+  // Creates and returns a new if-statement of the form:
+  //
+  //    if (tint_discard) {
+  //      return <default value>;
+  //    }
+  //
+  // or if `stmt` is in a entry point function:
+  //
+  //    if (tint_discard) {
+  //      tint_discard_func();
+  //      return <default value>;
+  //    }
+  //
+  const ast::IfStatement* IfDiscardReturn(const ast::Statement* stmt) {
+    ast::StatementList stmts;
+
+    // For entry point functions, also emit the discard statement
+    if (IsInEntryPointFunc(stmt)) {
+      stmts.emplace_back(CallDiscardFunc());
+    }
+
+    stmts.emplace_back(Return(stmt));
+
+    auto var_name = ModuleDiscardVarName();
+    return b.If(var_name, b.Block(stmts));
+  }
+
+  // Hoists `sem_expr` to a let followed by an `IfDiscardReturn` before `stmt`.
+  // For example, if `stmt` is:
+  //
+  //    return f();
+  //
+  // This function will transform this to:
+  //
+  //    let t1 = f();
+  //    if (tint_discard) {
+  //      return;
+  //    }
+  //    return t1;
+  //
+  const ast::Statement* HoistAndInsertBefore(const ast::Statement* stmt,
+                                             const sem::Expression* sem_expr) {
+    auto* expr = sem_expr->Declaration();
+
+    auto ip = GetInsertionPoint(stmt);
+    auto var_name = b.Sym();
+    auto* decl = b.Decl(b.Var(var_name, nullptr, ctx.Clone(expr)));
+    ctx.InsertBefore(ip.first->Declaration()->statements, ip.second, decl);
+
+    ctx.InsertBefore(ip.first->Declaration()->statements, ip.second,
+                     IfDiscardReturn(stmt));
+
+    auto* var_expr = b.Expr(var_name);
+
+    // Special handling for CallStatement as we can only replace its expression
+    // with a CallExpression.
+    if (stmt->Is<ast::CallStatement>()) {
+      // We could replace the call statement with no statement, but we can't do
+      // that with transforms (yet), so just return a phony assignment.
+      return b.Assign(b.Phony(), var_expr);
+    }
+
+    ctx.Replace(expr, var_expr);
+    return ctx.CloneWithoutTransform(stmt);
+  }
+
+  // Returns true if `stmt` is a for-loop initializer statement.
+  bool IsForLoopInitStatement(const ast::Statement* stmt) {
+    if (auto* sem_stmt = sem.Get(stmt)) {
+      if (auto* sem_fl = As<sem::ForLoopStatement>(sem_stmt->Parent())) {
+        return sem_fl->Declaration()->initializer == stmt;
+      }
+    }
+    return false;
+  }
+
+  // Inserts an `IfDiscardReturn` after `stmt` if possible (i.e. `stmt` is not
+  // in a for-loop init), otherwise falls back to HoistAndInsertBefore, hoisting
+  // `sem_expr` to a let followed by an `IfDiscardReturn` before `stmt`.
+  //
+  // For example, if `stmt` is:
+  //
+  //    let r = f();
+  //
+  // This function will transform this to:
+  //
+  //    let r = f();
+  //    if (tint_discard) {
+  //      return;
+  //    }
+  const ast::Statement* TryInsertAfter(const ast::Statement* stmt,
+                                       const sem::Expression* sem_expr) {
+    // If `stmt` is the init of a for-loop, hoist and insert before instead.
+    if (IsForLoopInitStatement(stmt)) {
+      return HoistAndInsertBefore(stmt, sem_expr);
+    }
+
+    auto ip = GetInsertionPoint(stmt);
+    ctx.InsertAfter(ip.first->Declaration()->statements, ip.second,
+                    IfDiscardReturn(stmt));
+    return nullptr;  // Don't replace current statement
+  }
+
+  // Replaces the input discard statement with either setting the module level
+  // discard bool ("tint_discard = true"), or calling the discard function
+  // ("tint_discard_func()"), followed by a default return statement.
+  //
+  // Replaces "discard;" with:
+  //
+  //    tint_discard = true;
+  //    return;
+  //
+  // Or if `stmt` is a entry point function, replaces with:
+  //
+  //    tint_discard_func();
+  //    return;
+  //
+  const ast::Statement* ReplaceDiscardStatement(
+      const ast::DiscardStatement* stmt) {
+    const ast::Statement* to_insert = nullptr;
+    if (IsInEntryPointFunc(stmt)) {
+      to_insert = CallDiscardFunc();
+    } else {
+      auto var_name = ModuleDiscardVarName();
+      to_insert = b.Assign(var_name, true);
+    }
+
+    auto ip = GetInsertionPoint(stmt);
+    ctx.InsertBefore(ip.first->Declaration()->statements, ip.second, to_insert);
+    return Return(stmt);
+  }
+
+  // Handle statement
+  const ast::Statement* Statement(const ast::Statement* stmt) {
+    return Switch(
+        stmt,
+        [&](const ast::DiscardStatement* s) -> const ast::Statement* {
+          return ReplaceDiscardStatement(s);
+        },
+        [&](const ast::AssignmentStatement* s) -> const ast::Statement* {
+          auto* sem_lhs = sem.Get(s->lhs);
+          auto* sem_rhs = sem.Get(s->rhs);
+          if (MayDiscard(sem_lhs)) {
+            if (MayDiscard(sem_rhs)) {
+              TINT_ICE(Transform, b.Diagnostics())
+                  << "Unexpected: both sides of assignment statement may "
+                     "discard. Make sure transform::PromoteSideEffectsToDecl "
+                     "was run first.";
+            }
+            return TryInsertAfter(s, sem_lhs);
+          } else if (MayDiscard(sem_rhs)) {
+            return TryInsertAfter(s, sem_rhs);
+          }
+          return nullptr;
+        },
+        [&](const ast::CallStatement* s) -> const ast::Statement* {
+          auto* sem_expr = sem.Get(s->expr);
+          if (!MayDiscard(sem_expr)) {
+            return nullptr;
+          }
+          return TryInsertAfter(s, sem_expr);
+        },
+        [&](const ast::ElseStatement* s) -> const ast::Statement* {
+          if (MayDiscard(sem.Get(s->condition))) {
+            TINT_ICE(Transform, b.Diagnostics())
+                << "Unexpected ElseIf condition that may discard. Make sure "
+                   "transform::PromoteSideEffectsToDecl was run first.";
+          }
+          return nullptr;
+        },
+        [&](const ast::ForLoopStatement* s) -> const ast::Statement* {
+          if (MayDiscard(sem.Get(s->condition))) {
+            TINT_ICE(Transform, b.Diagnostics())
+                << "Unexpected ForLoopStatement condition that may discard. "
+                   "Make sure transform::PromoteSideEffectsToDecl was run "
+                   "first.";
+          }
+          return nullptr;
+        },
+        [&](const ast::IfStatement* s) -> const ast::Statement* {
+          auto* sem_expr = sem.Get(s->condition);
+          if (!MayDiscard(sem_expr)) {
+            return nullptr;
+          }
+          return HoistAndInsertBefore(s, sem_expr);
+        },
+        [&](const ast::ReturnStatement* s) -> const ast::Statement* {
+          auto* sem_expr = sem.Get(s->value);
+          if (!MayDiscard(sem_expr)) {
+            return nullptr;
+          }
+          return HoistAndInsertBefore(s, sem_expr);
+        },
+        [&](const ast::SwitchStatement* s) -> const ast::Statement* {
+          auto* sem_expr = sem.Get(s->condition);
+          if (!MayDiscard(sem_expr)) {
+            return nullptr;
+          }
+          return HoistAndInsertBefore(s, sem_expr);
+        },
+        [&](const ast::VariableDeclStatement* s) -> const ast::Statement* {
+          auto* var = s->variable;
+          if (!var->constructor) {
+            return nullptr;
+          }
+          auto* sem_expr = sem.Get(var->constructor);
+          if (!MayDiscard(sem_expr)) {
+            return nullptr;
+          }
+          return TryInsertAfter(s, sem_expr);
+        });
+  }
+
+ public:
+  /// Constructor
+  /// @param ctx_in the context
+  explicit State(CloneContext& ctx_in)
+      : ctx(ctx_in), b(*ctx_in.dst), sem(ctx_in.src->Sem()) {}
+
+  /// Runs the transform
+  void Run() {
+    ctx.ReplaceAll(
+        [&](const ast::BlockStatement* block) -> const ast::Statement* {
+          // If this block is for an else-if statement, process the else-if now
+          // before processing its block statements.
+          // NOTE: we can't replace else statements at this point - this would
+          // need to be done when replacing the parent if-statement. However, in
+          // this transform, we don't ever expect to need to do this as else-ifs
+          // are converted to else { if } by PromoteSideEffectsToDecl, so this
+          // is only for validation.
+          if (auto* sem_else = ParentAs<sem::ElseStatement>(block)) {
+            if (auto* new_stmt = Statement(sem_else->Declaration())) {
+              TINT_ASSERT(Transform, new_stmt == nullptr);
+              return nullptr;
+            }
+          }
+
+          // Iterate block statements and replace them as needed.
+          for (auto* stmt : block->statements) {
+            if (auto* new_stmt = Statement(stmt)) {
+              ctx.Replace(stmt, new_stmt);
+            }
+
+            // Handle for loops, as they are the only other AST node that
+            // contains statements outside of BlockStatements.
+            if (auto* fl = stmt->As<ast::ForLoopStatement>()) {
+              if (auto* new_stmt = Statement(fl->initializer)) {
+                ctx.Replace(fl->initializer, new_stmt);
+              }
+              if (auto* new_stmt = Statement(fl->continuing)) {
+                // NOTE: Should never reach here as we cannot discard in a
+                // continuing block.
+                ctx.Replace(fl->continuing, new_stmt);
+              }
+            }
+          }
+
+          return nullptr;
+        });
+
+    ctx.Clone();
+  }
+};
+
+}  // namespace
+
+UnwindDiscardFunctions::UnwindDiscardFunctions() = default;
+UnwindDiscardFunctions::~UnwindDiscardFunctions() = default;
+
+void UnwindDiscardFunctions::Run(CloneContext& ctx,
+                                 const DataMap&,
+                                 DataMap&) const {
+  State state(ctx);
+  state.Run();
+}
+
+bool UnwindDiscardFunctions::ShouldRun(const Program* program,
+                                       const DataMap& /*data*/) const {
+  auto& sem = program->Sem();
+  for (auto* f : program->AST().Functions()) {
+    if (sem.Get(f)->Behaviors().Contains(sem::Behavior::kDiscard)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+}  // namespace tint::transform
diff --git a/src/tint/transform/unwind_discard_functions.h b/src/tint/transform/unwind_discard_functions.h
new file mode 100644
index 0000000..42bbecb
--- /dev/null
+++ b/src/tint/transform/unwind_discard_functions.h
@@ -0,0 +1,68 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_TRANSFORM_UNWIND_DISCARD_FUNCTIONS_H_
+#define SRC_TINT_TRANSFORM_UNWIND_DISCARD_FUNCTIONS_H_
+
+#include "src/tint/transform/transform.h"
+
+namespace tint::transform {
+
+/// This transform is responsible for implementing discard semantics as per the
+/// WGSL specification: https://gpuweb.github.io/gpuweb/wgsl/#discard-statement
+///
+/// Not all backend languages implement discard this way (e.g. HLSL), so this
+/// transform does the following:
+///
+/// * Replaces discard statements with setting a module-level boolean
+/// "tint_discard" to true and returning immediately.
+/// * Wherever calls are made to discarding functions (directly or
+/// transitively), it inserts a check afterwards for if "tint_discard" is true,
+/// to return immediately.
+/// * Finally, entry point functions that call discarding functions
+/// emit a call to "tint_discard_func()" that contains the sole discard
+/// statement.
+///
+/// @note Depends on the following transforms to have been run first:
+/// * PromoteSideEffectsToDecl
+class UnwindDiscardFunctions
+    : public Castable<UnwindDiscardFunctions, Transform> {
+ public:
+  /// Constructor
+  UnwindDiscardFunctions();
+
+  /// Destructor
+  ~UnwindDiscardFunctions() override;
+
+ protected:
+  /// Runs the transform using the CloneContext built for transforming a
+  /// program. Run() is responsible for calling Clone() on the CloneContext.
+  /// @param ctx the CloneContext primed with the input program and
+  /// ProgramBuilder
+  /// @param inputs optional extra transform-specific input data
+  /// @param outputs optional extra transform-specific output data
+  void Run(CloneContext& ctx,
+           const DataMap& inputs,
+           DataMap& outputs) const override;
+
+  /// @param program the program to inspect
+  /// @param data optional extra transform-specific input data
+  /// @returns true if this transform should be run for the given program
+  bool ShouldRun(const Program* program,
+                 const DataMap& data = {}) const override;
+};
+
+}  // namespace tint::transform
+
+#endif  // SRC_TINT_TRANSFORM_UNWIND_DISCARD_FUNCTIONS_H_
diff --git a/src/tint/transform/unwind_discard_functions_test.cc b/src/tint/transform/unwind_discard_functions_test.cc
new file mode 100644
index 0000000..ae0a1f1
--- /dev/null
+++ b/src/tint/transform/unwind_discard_functions_test.cc
@@ -0,0 +1,1421 @@
+// Copyright 2022 The Tint Authors.
+//
+// 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 "src/tint/transform/unwind_discard_functions.h"
+#include "src/tint/transform/promote_side_effects_to_decl.h"
+#include "src/tint/transform/test_helper.h"
+
+namespace tint {
+namespace transform {
+namespace {
+
+using UnwindDiscardFunctionsTest = TransformTest;
+
+TEST_F(UnwindDiscardFunctionsTest, EmptyModule) {
+  auto* src = "";
+  auto* expect = src;
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, ShouldRun_NoDiscardFunc) {
+  auto* src = R"(
+fn f() {
+}
+)";
+
+  EXPECT_FALSE(ShouldRun<UnwindDiscardFunctions>(src));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, SingleDiscardFunc_NoCall) {
+  auto* src = R"(
+fn f() {
+  discard;
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() {
+  tint_discard = true;
+  return;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, MultipleDiscardFuncs_NoCall) {
+  auto* src = R"(
+fn f() {
+  discard;
+  let marker1 = 0;
+}
+
+fn g() {
+  discard;
+  let marker1 = 0;
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() {
+  tint_discard = true;
+  return;
+  let marker1 = 0;
+}
+
+fn g() {
+  tint_discard = true;
+  return;
+  let marker1 = 0;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Call_VoidReturn) {
+  auto* src = R"(
+fn f() {
+  discard;
+  let marker1 = 0;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  f();
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() {
+  tint_discard = true;
+  return;
+  let marker1 = 0;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Call_NonVoidReturn) {
+  auto* src = R"(
+struct S {
+  x : i32,
+  y : i32,
+};
+
+fn f() -> S {
+  if (true) {
+    discard;
+  }
+  let marker1 = 0;
+  var s : S;
+  return s;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  f();
+  let marker2 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+struct S {
+  x : i32,
+  y : i32,
+}
+
+var<private> tint_discard : bool = false;
+
+fn f() -> S {
+  if (true) {
+    tint_discard = true;
+    return S();
+  }
+  let marker1 = 0;
+  var s : S;
+  return s;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker2 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Call_Nested) {
+  auto* src = R"(
+fn f() -> i32 {
+  let marker1 = 0;
+  if (true) {
+    discard;
+  }
+  let marker2 = 0;
+  return 0;
+}
+
+fn g() -> i32 {
+  let marker1 = 0;
+  f();
+  let marker2 = 0;
+  return 0;
+}
+
+fn h() -> i32{
+  let marker1 = 0;
+  g();
+  let marker2 = 0;
+  return 0;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  h();
+  let marker2 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  let marker1 = 0;
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  let marker2 = 0;
+  return 0;
+}
+
+fn g() -> i32 {
+  let marker1 = 0;
+  f();
+  if (tint_discard) {
+    return i32();
+  }
+  let marker2 = 0;
+  return 0;
+}
+
+fn h() -> i32 {
+  let marker1 = 0;
+  g();
+  if (tint_discard) {
+    return i32();
+  }
+  let marker2 = 0;
+  return 0;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  h();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker2 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Call_Multiple) {
+  auto* src = R"(
+fn f() {
+  discard;
+  let marker1 = 0;
+}
+
+fn g() {
+  discard;
+  let marker1 = 0;
+}
+
+fn h() {
+  discard;
+  let marker1 = 0;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  f();
+  let marker2 = 0;
+  g();
+  let marker3 = 0;
+  h();
+  let marker4 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() {
+  tint_discard = true;
+  return;
+  let marker1 = 0;
+}
+
+fn g() {
+  tint_discard = true;
+  return;
+  let marker1 = 0;
+}
+
+fn h() {
+  tint_discard = true;
+  return;
+  let marker1 = 0;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker2 = 0;
+  g();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker3 = 0;
+  h();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker4 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Call_DiscardFuncDeclaredBelow) {
+  auto* src = R"(
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  f();
+  let marker1 = 0;
+  return vec4<f32>();
+}
+
+fn f() {
+  discard;
+  let marker1 = 0;
+}
+)";
+  auto* expect = R"(
+fn tint_discard_func() {
+  discard;
+}
+
+var<private> tint_discard : bool = false;
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker1 = 0;
+  return vec4<f32>();
+}
+
+fn f() {
+  tint_discard = true;
+  return;
+  let marker1 = 0;
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, If) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  if (f() == 42) {
+    let marker1 = 0;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let tint_symbol = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  if ((tint_symbol == 42)) {
+    let marker1 = 0;
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, ElseIf) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  if (true) {
+    let marker1 = 0;
+  } else if (f() == 42) {
+    let marker2 = 0;
+  } else if (true) {
+    let marker3 = 0;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  if (true) {
+    let marker1 = 0;
+  } else {
+    let tint_symbol = f();
+    if (tint_discard) {
+      tint_discard_func();
+      return vec4<f32>();
+    }
+    if ((tint_symbol == 42)) {
+      let marker2 = 0;
+    } else if (true) {
+      let marker3 = 0;
+    }
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, ForLoop_Init_Assignment) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  var a = 0;
+  for (a = f(); ; ) {
+    let marker2 = 0;
+    break;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  var a = 0;
+  var tint_symbol = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  for(a = tint_symbol; ; ) {
+    let marker2 = 0;
+    break;
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, ForLoop_Init_Call) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  for (f(); ; ) {
+    let marker2 = 0;
+    break;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  var tint_symbol = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  for(_ = tint_symbol; ; ) {
+    let marker2 = 0;
+    break;
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, ForLoop_Init_VariableDecl) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  for (let i = f(); ; ) {
+    let marker2 = 0;
+    break;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  var tint_symbol = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  for(let i = tint_symbol; ; ) {
+    let marker2 = 0;
+    break;
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, ForLoop_Cond) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  for (; f() == 42; ) {
+    let marker2 = 0;
+    break;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  loop {
+    let tint_symbol = f();
+    if (tint_discard) {
+      tint_discard_func();
+      return vec4<f32>();
+    }
+    if (!((tint_symbol == 42))) {
+      break;
+    }
+    {
+      let marker2 = 0;
+      break;
+    }
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, ForLoop_Cont) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  for (; ; f()) {
+    let marker2 = 0;
+    break;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect =
+      R"(test:12:12 error: cannot call a function that may discard inside a continuing block
+  for (; ; f()) {
+           ^
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Switch) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  switch (f()) {
+    case 0: {
+      let marker1 = 0;
+    }
+    case 1: {
+      let marker2 = 0;
+    }
+    case 42: {
+      let marker3 = 0;
+    }
+    default: {
+      let marker4 = 0;
+    }
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  var tint_symbol = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  switch(tint_symbol) {
+    case 0: {
+      let marker1 = 0;
+    }
+    case 1: {
+      let marker2 = 0;
+    }
+    case 42: {
+      let marker3 = 0;
+    }
+    default: {
+      let marker4 = 0;
+    }
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Return) {
+  auto* src = R"(
+struct S {
+  x : i32,
+  y : i32,
+};
+
+fn f() -> S {
+  if (true) {
+    discard;
+  }
+  var s : S;
+  return s;
+}
+
+fn g() -> S {
+  return f();
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  g();
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+struct S {
+  x : i32,
+  y : i32,
+}
+
+var<private> tint_discard : bool = false;
+
+fn f() -> S {
+  if (true) {
+    tint_discard = true;
+    return S();
+  }
+  var s : S;
+  return s;
+}
+
+fn g() -> S {
+  var tint_symbol = f();
+  if (tint_discard) {
+    return S();
+  }
+  return tint_symbol;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let marker1 = 0;
+  g();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, VariableDecl) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  var a = f();
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  var a = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Assignment_RightDiscard) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 42;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  var a : i32;
+  a = f();
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 42;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  var a : i32;
+  a = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Assignment_LeftDiscard) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  var b = array<i32, 10>();
+  b[f()] = 10;
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  var b = array<i32, 10>();
+  let tint_symbol = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  b[tint_symbol] = 10;
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Assignment_BothDiscard) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+fn g() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  var b = array<i32, 10>();
+  b[f()] = g();
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn g() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  var b = array<i32, 10>();
+  let tint_symbol = g();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let tint_symbol_1 = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  b[tint_symbol_1] = tint_symbol;
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Binary_Arith_MultipleDiscardFuncs) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+fn g() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+fn h() -> i32{
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  if ((f() + g() + h()) == 0) {
+    let marker1 = 0;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn g() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn h() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let tint_symbol = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let tint_symbol_1 = g();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  let tint_symbol_2 = h();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  if ((((tint_symbol + tint_symbol_1) + tint_symbol_2) == 0)) {
+    let marker1 = 0;
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, Binary_Logical_MultipleDiscardFuncs) {
+  auto* src = R"(
+fn f() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+fn g() -> i32 {
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+fn h() -> i32{
+  if (true) {
+    discard;
+  }
+  return 0;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  if (f() == 1 && g() == 2 && h() == 3) {
+    let marker1 = 0;
+  }
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard : bool = false;
+
+fn f() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn g() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn h() -> i32 {
+  if (true) {
+    tint_discard = true;
+    return i32();
+  }
+  return 0;
+}
+
+fn tint_discard_func() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  let tint_symbol_2 = f();
+  if (tint_discard) {
+    tint_discard_func();
+    return vec4<f32>();
+  }
+  var tint_symbol_1 = (tint_symbol_2 == 1);
+  if (tint_symbol_1) {
+    let tint_symbol_3 = g();
+    if (tint_discard) {
+      tint_discard_func();
+      return vec4<f32>();
+    }
+    tint_symbol_1 = (tint_symbol_3 == 2);
+  }
+  var tint_symbol = tint_symbol_1;
+  if (tint_symbol) {
+    let tint_symbol_4 = h();
+    if (tint_discard) {
+      tint_discard_func();
+      return vec4<f32>();
+    }
+    tint_symbol = (tint_symbol_4 == 3);
+  }
+  if (tint_symbol) {
+    let marker1 = 0;
+  }
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(UnwindDiscardFunctionsTest, EnsureNoSymbolCollision) {
+  auto* src = R"(
+var<private> tint_discard_func : i32;
+var<private> tint_discard : i32;
+
+fn f() {
+  discard;
+  let marker1 = 0;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in: vec4<f32>) -> @location(0) vec4<f32> {
+  f();
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+  auto* expect = R"(
+var<private> tint_discard_func : i32;
+
+var<private> tint_discard : i32;
+
+var<private> tint_discard_1 : bool = false;
+
+fn f() {
+  tint_discard_1 = true;
+  return;
+  let marker1 = 0;
+}
+
+fn tint_discard_func_1() {
+  discard;
+}
+
+@stage(fragment)
+fn main(@builtin(position) coord_in : vec4<f32>) -> @location(0) vec4<f32> {
+  f();
+  if (tint_discard_1) {
+    tint_discard_func_1();
+    return vec4<f32>();
+  }
+  let marker1 = 0;
+  return vec4<f32>();
+}
+)";
+
+  DataMap data;
+  auto got = Run<PromoteSideEffectsToDecl, UnwindDiscardFunctions>(src, data);
+
+  EXPECT_EQ(expect, str(got));
+}
+
+}  // namespace
+}  // namespace transform
+}  // namespace tint
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index 909fcff..bf35449 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -62,6 +62,7 @@
 #include "src/tint/transform/remove_phonies.h"
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/transform/unshadow.h"
+#include "src/tint/transform/unwind_discard_functions.h"
 #include "src/tint/transform/zero_init_workgroup_memory.h"
 #include "src/tint/utils/defer.h"
 #include "src/tint/utils/map.h"
@@ -187,6 +188,7 @@
   // only accessed directly via member accessors.
   manager.Add<transform::NumWorkgroupsFromUniform>();
   manager.Add<transform::PromoteSideEffectsToDecl>();
+  manager.Add<transform::UnwindDiscardFunctions>();
   manager.Add<transform::SimplifyPointers>();
   manager.Add<transform::RemovePhonies>();
   // ArrayLengthFromUniform must come after InlinePointerLets and Simplify, as
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 78388dd..3e8d38b 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -68,6 +68,7 @@
 #include "src/tint/transform/remove_phonies.h"
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/transform/unshadow.h"
+#include "src/tint/transform/unwind_discard_functions.h"
 #include "src/tint/transform/vectorize_scalar_matrix_constructors.h"
 #include "src/tint/transform/wrap_arrays_in_structs.h"
 #include "src/tint/transform/zero_init_workgroup_memory.h"
@@ -174,6 +175,7 @@
   }
   manager.Add<transform::CanonicalizeEntryPointIO>();
   manager.Add<transform::PromoteSideEffectsToDecl>();
+  manager.Add<transform::UnwindDiscardFunctions>();
   manager.Add<transform::PromoteInitializersToConstVar>();
 
   manager.Add<transform::VectorizeScalarMatrixConstructors>();
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index 3b3bdb7..1bf650f 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -52,6 +52,7 @@
 #include "src/tint/transform/remove_unreachable_statements.h"
 #include "src/tint/transform/simplify_pointers.h"
 #include "src/tint/transform/unshadow.h"
+#include "src/tint/transform/unwind_discard_functions.h"
 #include "src/tint/transform/var_for_dynamic_index.h"
 #include "src/tint/transform/vectorize_scalar_matrix_constructors.h"
 #include "src/tint/transform/zero_init_workgroup_memory.h"
@@ -278,6 +279,7 @@
   }
   manager.Add<transform::RemoveUnreachableStatements>();
   manager.Add<transform::PromoteSideEffectsToDecl>();
+  manager.Add<transform::UnwindDiscardFunctions>();
   manager.Add<transform::SimplifyPointers>();  // Required for arrayLength()
   manager.Add<transform::FoldConstants>();
   manager.Add<transform::VectorizeScalarMatrixConstructors>();
diff --git a/test/tint/BUILD.gn b/test/tint/BUILD.gn
index 834d948..7f6ec7e 100644
--- a/test/tint/BUILD.gn
+++ b/test/tint/BUILD.gn
@@ -338,6 +338,7 @@
     "../../src/tint/transform/test_helper.h",
     "../../src/tint/transform/transform_test.cc",
     "../../src/tint/transform/unshadow_test.cc",
+    "../../src/tint/transform/unwind_discard_functions_test.cc",
     "../../src/tint/transform/utils/hoist_to_decl_before_test.cc",
     "../../src/tint/transform/var_for_dynamic_index_test.cc",
     "../../src/tint/transform/vectorize_scalar_matrix_constructors_test.cc",
diff --git a/test/tint/bug/dawn/947.wgsl.expected.glsl b/test/tint/bug/dawn/947.wgsl.expected.glsl
index f3e2440..71b989e 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.glsl
+++ b/test/tint/bug/dawn/947.wgsl.expected.glsl
@@ -52,19 +52,29 @@
   vec4 position;
 };
 
+bool tint_discard = false;
 uniform highp sampler2D myTexture_mySampler;
 
 vec4 fs_main(vec2 texcoord) {
   vec2 clampedTexcoord = clamp(texcoord, vec2(0.0f, 0.0f), vec2(1.0f, 1.0f));
   if (!(all(equal(clampedTexcoord, texcoord)))) {
-    discard;
+    tint_discard = true;
+    return vec4(0.0f, 0.0f, 0.0f, 0.0f);
   }
   vec4 srcColor = texture(myTexture_mySampler, texcoord);
   return srcColor;
 }
 
+void tint_discard_func() {
+  discard;
+}
+
 void main() {
   vec4 inner_result = fs_main(texcoord_1);
+  if (tint_discard) {
+    tint_discard_func();
+    return;
+  }
   value = inner_result;
   return;
 }
diff --git a/test/tint/bug/dawn/947.wgsl.expected.hlsl b/test/tint/bug/dawn/947.wgsl.expected.hlsl
index da0ff6a..7bcadcb 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.hlsl
+++ b/test/tint/bug/dawn/947.wgsl.expected.hlsl
@@ -45,21 +45,29 @@
   float4 value : SV_Target0;
 };
 
+static bool tint_discard = false;
+
 float4 fs_main_inner(float2 texcoord) {
-  if (true) {
-    float2 clampedTexcoord = clamp(texcoord, float2(0.0f, 0.0f), float2(1.0f, 1.0f));
-    if (!(all((clampedTexcoord == texcoord)))) {
-      discard;
-    }
-    float4 srcColor = myTexture.Sample(mySampler, texcoord);
-    return srcColor;
+  float2 clampedTexcoord = clamp(texcoord, float2(0.0f, 0.0f), float2(1.0f, 1.0f));
+  if (!(all((clampedTexcoord == texcoord)))) {
+    tint_discard = true;
+    return float4(0.0f, 0.0f, 0.0f, 0.0f);
   }
-  float4 unused;
-  return unused;
+  float4 srcColor = myTexture.Sample(mySampler, texcoord);
+  return srcColor;
+}
+
+void tint_discard_func() {
+  discard;
 }
 
 tint_symbol_5 fs_main(tint_symbol_4 tint_symbol_3) {
   const float4 inner_result_1 = fs_main_inner(tint_symbol_3.texcoord);
+  if (tint_discard) {
+    tint_discard_func();
+    const tint_symbol_5 tint_symbol_8 = (tint_symbol_5)0;
+    return tint_symbol_8;
+  }
   tint_symbol_5 wrapper_result_1 = (tint_symbol_5)0;
   wrapper_result_1.value = inner_result_1;
   return wrapper_result_1;
diff --git a/test/tint/bug/dawn/947.wgsl.expected.msl b/test/tint/bug/dawn/947.wgsl.expected.msl
index 22bfdd3..15efcd0 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.msl
+++ b/test/tint/bug/dawn/947.wgsl.expected.msl
@@ -20,21 +20,21 @@
   float2 arr[3];
 };
 
-VertexOutputs vs_main_inner(uint VertexIndex, const constant Uniforms* const tint_symbol_4) {
+VertexOutputs vs_main_inner(uint VertexIndex, const constant Uniforms* const tint_symbol_5) {
   tint_array_wrapper texcoord = {.arr={float2(-0.5f, 0.0f), float2(1.5f, 0.0f), float2(0.5f, 2.0f)}};
   VertexOutputs output = {};
   output.position = float4(((texcoord.arr[VertexIndex] * 2.0f) - float2(1.0f, 1.0f)), 0.0f, 1.0f);
-  bool flipY = ((*(tint_symbol_4)).u_scale[1] < 0.0f);
+  bool flipY = ((*(tint_symbol_5)).u_scale[1] < 0.0f);
   if (flipY) {
-    output.texcoords = ((((texcoord.arr[VertexIndex] * (*(tint_symbol_4)).u_scale) + (*(tint_symbol_4)).u_offset) * float2(1.0f, -1.0f)) + float2(0.0f, 1.0f));
+    output.texcoords = ((((texcoord.arr[VertexIndex] * (*(tint_symbol_5)).u_scale) + (*(tint_symbol_5)).u_offset) * float2(1.0f, -1.0f)) + float2(0.0f, 1.0f));
   } else {
-    output.texcoords = ((((texcoord.arr[VertexIndex] * float2(1.0f, -1.0f)) + float2(0.0f, 1.0f)) * (*(tint_symbol_4)).u_scale) + (*(tint_symbol_4)).u_offset);
+    output.texcoords = ((((texcoord.arr[VertexIndex] * float2(1.0f, -1.0f)) + float2(0.0f, 1.0f)) * (*(tint_symbol_5)).u_scale) + (*(tint_symbol_5)).u_offset);
   }
   return output;
 }
 
-vertex tint_symbol vs_main(const constant Uniforms* tint_symbol_5 [[buffer(0)]], uint VertexIndex [[vertex_id]]) {
-  VertexOutputs const inner_result = vs_main_inner(VertexIndex, tint_symbol_5);
+vertex tint_symbol vs_main(const constant Uniforms* tint_symbol_6 [[buffer(0)]], uint VertexIndex [[vertex_id]]) {
+  VertexOutputs const inner_result = vs_main_inner(VertexIndex, tint_symbol_6);
   tint_symbol wrapper_result = {};
   wrapper_result.texcoords = inner_result.texcoords;
   wrapper_result.position = inner_result.position;
@@ -49,17 +49,28 @@
   float4 value [[color(0)]];
 };
 
-float4 fs_main_inner(float2 texcoord, texture2d<float, access::sample> tint_symbol_6, sampler tint_symbol_7) {
+float4 fs_main_inner(float2 texcoord, thread bool* const tint_symbol_7, texture2d<float, access::sample> tint_symbol_8, sampler tint_symbol_9) {
   float2 clampedTexcoord = clamp(texcoord, float2(0.0f, 0.0f), float2(1.0f, 1.0f));
   if (!(all((clampedTexcoord == texcoord)))) {
-    discard_fragment();
+    *(tint_symbol_7) = true;
+    return float4();
   }
-  float4 srcColor = tint_symbol_6.sample(tint_symbol_7, texcoord);
+  float4 srcColor = tint_symbol_8.sample(tint_symbol_9, texcoord);
   return srcColor;
 }
 
-fragment tint_symbol_3 fs_main(texture2d<float, access::sample> tint_symbol_8 [[texture(0)]], sampler tint_symbol_9 [[sampler(0)]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
-  float4 const inner_result_1 = fs_main_inner(tint_symbol_1.texcoord, tint_symbol_8, tint_symbol_9);
+void tint_discard_func() {
+  discard_fragment();
+}
+
+fragment tint_symbol_3 fs_main(texture2d<float, access::sample> tint_symbol_11 [[texture(0)]], sampler tint_symbol_12 [[sampler(0)]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  thread bool tint_symbol_10 = false;
+  float4 const inner_result_1 = fs_main_inner(tint_symbol_1.texcoord, &(tint_symbol_10), tint_symbol_11, tint_symbol_12);
+  if (tint_symbol_10) {
+    tint_discard_func();
+    tint_symbol_3 const tint_symbol_4 = {};
+    return tint_symbol_4;
+  }
   tint_symbol_3 wrapper_result_1 = {};
   wrapper_result_1.value = inner_result_1;
   return wrapper_result_1;
diff --git a/test/tint/bug/dawn/947.wgsl.expected.spvasm b/test/tint/bug/dawn/947.wgsl.expected.spvasm
index cfe88b2..df48a9a 100644
--- a/test/tint/bug/dawn/947.wgsl.expected.spvasm
+++ b/test/tint/bug/dawn/947.wgsl.expected.spvasm
@@ -1,10 +1,10 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 0
-; Bound: 137
+; Bound: 140
 ; Schema: 0
                OpCapability Shader
-        %116 = OpExtInstImport "GLSL.std.450"
+        %118 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Vertex %vs_main "vs_main" %VertexIndex_1 %texcoords_1 %position_1 %vertex_point_size
                OpEntryPoint Fragment %fs_main "fs_main" %texcoord_1 %value
@@ -30,6 +30,7 @@
                OpName %output "output"
                OpName %flipY "flipY"
                OpName %vs_main "vs_main"
+               OpName %tint_discard_func "tint_discard_func"
                OpName %fs_main_inner "fs_main_inner"
                OpName %texcoord_0 "texcoord"
                OpName %clampedTexcoord "clampedTexcoord"
@@ -114,10 +115,10 @@
          %89 = OpConstantComposite %v2float %float_0 %float_1
        %void = OpTypeVoid
         %103 = OpTypeFunction %void
-        %111 = OpTypeFunction %v4float %v2float
-        %117 = OpConstantComposite %v2float %float_0 %float_0
+        %113 = OpTypeFunction %v4float %v2float
+        %119 = OpConstantComposite %v2float %float_0 %float_0
      %v2bool = OpTypeVector %bool 2
-        %129 = OpTypeSampledImage %27
+        %132 = OpTypeSampledImage %27
 %vs_main_inner = OpFunction %VertexOutputs None %28
 %VertexIndex = OpFunctionParameter %uint
          %32 = OpLabel
@@ -184,34 +185,39 @@
                OpStore %vertex_point_size %float_1
                OpReturn
                OpFunctionEnd
-%fs_main_inner = OpFunction %v4float None %111
+%tint_discard_func = OpFunction %void None %103
+        %112 = OpLabel
+               OpKill
+               OpFunctionEnd
+%fs_main_inner = OpFunction %v4float None %113
  %texcoord_0 = OpFunctionParameter %v2float
-        %114 = OpLabel
+        %116 = OpLabel
 %clampedTexcoord = OpVariable %_ptr_Function_v2float Function %8
    %srcColor = OpVariable %_ptr_Function_v4float Function %12
-        %115 = OpExtInst %v2float %116 NClamp %texcoord_0 %117 %58
-               OpStore %clampedTexcoord %115
-        %121 = OpLoad %v2float %clampedTexcoord
-        %122 = OpFOrdEqual %v2bool %121 %texcoord_0
-        %120 = OpAll %bool %122
-        %119 = OpLogicalNot %bool %120
-               OpSelectionMerge %124 None
-               OpBranchConditional %119 %125 %124
-        %125 = OpLabel
-               OpKill
-        %124 = OpLabel
-        %127 = OpLoad %24 %mySampler
-        %128 = OpLoad %27 %myTexture
-        %130 = OpSampledImage %129 %128 %127
-        %126 = OpImageSampleImplicitLod %v4float %130 %texcoord_0
-               OpStore %srcColor %126
-        %132 = OpLoad %v4float %srcColor
-               OpReturnValue %132
+        %117 = OpExtInst %v2float %118 NClamp %texcoord_0 %119 %58
+               OpStore %clampedTexcoord %117
+        %123 = OpLoad %v2float %clampedTexcoord
+        %124 = OpFOrdEqual %v2bool %123 %texcoord_0
+        %122 = OpAll %bool %124
+        %121 = OpLogicalNot %bool %122
+               OpSelectionMerge %126 None
+               OpBranchConditional %121 %127 %126
+        %127 = OpLabel
+        %128 = OpFunctionCall %void %tint_discard_func
+               OpReturnValue %12
+        %126 = OpLabel
+        %130 = OpLoad %24 %mySampler
+        %131 = OpLoad %27 %myTexture
+        %133 = OpSampledImage %132 %131 %130
+        %129 = OpImageSampleImplicitLod %v4float %133 %texcoord_0
+               OpStore %srcColor %129
+        %135 = OpLoad %v4float %srcColor
+               OpReturnValue %135
                OpFunctionEnd
     %fs_main = OpFunction %void None %103
-        %134 = OpLabel
-        %136 = OpLoad %v2float %texcoord_1
-        %135 = OpFunctionCall %v4float %fs_main_inner %136
-               OpStore %value %135
+        %137 = OpLabel
+        %139 = OpLoad %v2float %texcoord_1
+        %138 = OpFunctionCall %v4float %fs_main_inner %139
+               OpStore %value %138
                OpReturn
                OpFunctionEnd
diff --git a/test/tint/bug/tint/1081.wgsl.expected.glsl b/test/tint/bug/tint/1081.wgsl.expected.glsl
index 3b4deae..5bef660 100644
--- a/test/tint/bug/tint/1081.wgsl.expected.glsl
+++ b/test/tint/bug/tint/1081.wgsl.expected.glsl
@@ -3,9 +3,11 @@
 
 layout(location = 1) flat in ivec3 x_1;
 layout(location = 2) out int value;
+bool tint_discard = false;
 int f(int x) {
   if ((x == 10)) {
-    discard;
+    tint_discard = true;
+    return 0;
   }
   return x;
 }
@@ -14,6 +16,9 @@
   int y = x.x;
   while (true) {
     int r = f(y);
+    if (tint_discard) {
+      return 0;
+    }
     if ((r == 0)) {
       break;
     }
@@ -21,8 +26,16 @@
   return y;
 }
 
+void tint_discard_func() {
+  discard;
+}
+
 void main() {
   int inner_result = tint_symbol(x_1);
+  if (tint_discard) {
+    tint_discard_func();
+    return;
+  }
   value = inner_result;
   return;
 }
diff --git a/test/tint/bug/tint/1081.wgsl.expected.hlsl b/test/tint/bug/tint/1081.wgsl.expected.hlsl
index ad9174c..60ace97 100644
--- a/test/tint/bug/tint/1081.wgsl.expected.hlsl
+++ b/test/tint/bug/tint/1081.wgsl.expected.hlsl
@@ -1,12 +1,11 @@
+static bool tint_discard = false;
+
 int f(int x) {
-  if (true) {
-    if ((x == 10)) {
-      discard;
-    }
-    return x;
+  if ((x == 10)) {
+    tint_discard = true;
+    return 0;
   }
-  int unused;
-  return unused;
+  return x;
 }
 
 struct tint_symbol_1 {
@@ -20,6 +19,9 @@
   int y = x.x;
   [loop] while (true) {
     const int r = f(y);
+    if (tint_discard) {
+      return 0;
+    }
     if ((r == 0)) {
       break;
     }
@@ -27,8 +29,17 @@
   return y;
 }
 
+void tint_discard_func() {
+  discard;
+}
+
 tint_symbol_2 main(tint_symbol_1 tint_symbol) {
   const int inner_result = main_inner(tint_symbol.x);
+  if (tint_discard) {
+    tint_discard_func();
+    const tint_symbol_2 tint_symbol_3 = (tint_symbol_2)0;
+    return tint_symbol_3;
+  }
   tint_symbol_2 wrapper_result = (tint_symbol_2)0;
   wrapper_result.value = inner_result;
   return wrapper_result;
diff --git a/test/tint/bug/tint/1081.wgsl.expected.msl b/test/tint/bug/tint/1081.wgsl.expected.msl
index 34ebe0c..8304df1 100644
--- a/test/tint/bug/tint/1081.wgsl.expected.msl
+++ b/test/tint/bug/tint/1081.wgsl.expected.msl
@@ -1,9 +1,10 @@
 #include <metal_stdlib>
 
 using namespace metal;
-int f(int x) {
+int f(int x, thread bool* const tint_symbol_5) {
   if ((x == 10)) {
-    discard_fragment();
+    *(tint_symbol_5) = true;
+    return int();
   }
   return x;
 }
@@ -16,10 +17,13 @@
   int value [[color(2)]];
 };
 
-int tint_symbol_inner(int3 x) {
+int tint_symbol_inner(int3 x, thread bool* const tint_symbol_6) {
   int y = x[0];
   while (true) {
-    int const r = f(y);
+    int const r = f(y, tint_symbol_6);
+    if (*(tint_symbol_6)) {
+      return int();
+    }
     if ((r == 0)) {
       break;
     }
@@ -27,8 +31,18 @@
   return y;
 }
 
+void tint_discard_func() {
+  discard_fragment();
+}
+
 fragment tint_symbol_3 tint_symbol(tint_symbol_2 tint_symbol_1 [[stage_in]]) {
-  int const inner_result = tint_symbol_inner(tint_symbol_1.x);
+  thread bool tint_symbol_7 = false;
+  int const inner_result = tint_symbol_inner(tint_symbol_1.x, &(tint_symbol_7));
+  if (tint_symbol_7) {
+    tint_discard_func();
+    tint_symbol_3 const tint_symbol_4 = {};
+    return tint_symbol_4;
+  }
   tint_symbol_3 wrapper_result = {};
   wrapper_result.value = inner_result;
   return wrapper_result;
diff --git a/test/tint/bug/tint/1081.wgsl.expected.spvasm b/test/tint/bug/tint/1081.wgsl.expected.spvasm
index 2f5ee3f..53e025c 100644
--- a/test/tint/bug/tint/1081.wgsl.expected.spvasm
+++ b/test/tint/bug/tint/1081.wgsl.expected.spvasm
@@ -1,7 +1,7 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 0
-; Bound: 41
+; Bound: 51
 ; Schema: 0
                OpCapability Shader
                OpMemoryModel Logical GLSL450
@@ -9,8 +9,10 @@
                OpExecutionMode %main OriginUpperLeft
                OpName %x_1 "x_1"
                OpName %value "value"
+               OpName %tint_discard "tint_discard"
                OpName %f "f"
                OpName %x "x"
+               OpName %tint_discard_func "tint_discard_func"
                OpName %main_inner "main_inner"
                OpName %x_0 "x"
                OpName %y "y"
@@ -25,55 +27,71 @@
 %_ptr_Output_int = OpTypePointer Output %int
           %7 = OpConstantNull %int
       %value = OpVariable %_ptr_Output_int Output %7
-          %8 = OpTypeFunction %int %int
-     %int_10 = OpConstant %int 10
        %bool = OpTypeBool
-         %17 = OpTypeFunction %int %v3int
+      %false = OpConstantFalse %bool
+%_ptr_Private_bool = OpTypePointer Private %bool
+%tint_discard = OpVariable %_ptr_Private_bool Private %false
+         %12 = OpTypeFunction %int %int
+     %int_10 = OpConstant %int 10
+       %true = OpConstantTrue %bool
+       %void = OpTypeVoid
+         %21 = OpTypeFunction %void
+         %25 = OpTypeFunction %int %v3int
 %_ptr_Function_int = OpTypePointer Function %int
       %int_0 = OpConstant %int 0
-       %void = OpTypeVoid
-         %35 = OpTypeFunction %void
-          %f = OpFunction %int None %8
+          %f = OpFunction %int None %12
           %x = OpFunctionParameter %int
-         %11 = OpLabel
-         %13 = OpIEqual %bool %x %int_10
-               OpSelectionMerge %15 None
-               OpBranchConditional %13 %16 %15
-         %16 = OpLabel
-               OpKill
          %15 = OpLabel
+         %17 = OpIEqual %bool %x %int_10
+               OpSelectionMerge %18 None
+               OpBranchConditional %17 %19 %18
+         %19 = OpLabel
+               OpStore %tint_discard %true
+               OpReturnValue %7
+         %18 = OpLabel
                OpReturnValue %x
                OpFunctionEnd
- %main_inner = OpFunction %int None %17
-        %x_0 = OpFunctionParameter %v3int
-         %20 = OpLabel
-          %y = OpVariable %_ptr_Function_int Function %7
-         %21 = OpCompositeExtract %int %x_0 0
-               OpStore %y %21
-               OpBranch %24
+%tint_discard_func = OpFunction %void None %21
          %24 = OpLabel
-               OpLoopMerge %25 %26 None
-               OpBranch %27
-         %27 = OpLabel
-         %29 = OpLoad %int %y
-         %28 = OpFunctionCall %int %f %29
-         %31 = OpIEqual %bool %28 %int_0
-               OpSelectionMerge %32 None
-               OpBranchConditional %31 %33 %32
-         %33 = OpLabel
-               OpBranch %25
-         %32 = OpLabel
-               OpBranch %26
-         %26 = OpLabel
-               OpBranch %24
-         %25 = OpLabel
-         %34 = OpLoad %int %y
-               OpReturnValue %34
+               OpKill
                OpFunctionEnd
-       %main = OpFunction %void None %35
-         %38 = OpLabel
-         %40 = OpLoad %v3int %x_1
-         %39 = OpFunctionCall %int %main_inner %40
-               OpStore %value %39
+ %main_inner = OpFunction %int None %25
+        %x_0 = OpFunctionParameter %v3int
+         %28 = OpLabel
+          %y = OpVariable %_ptr_Function_int Function %7
+         %29 = OpCompositeExtract %int %x_0 0
+               OpStore %y %29
+               OpBranch %32
+         %32 = OpLabel
+               OpLoopMerge %33 %34 None
+               OpBranch %35
+         %35 = OpLabel
+         %37 = OpLoad %int %y
+         %36 = OpFunctionCall %int %f %37
+         %38 = OpLoad %bool %tint_discard
+               OpSelectionMerge %39 None
+               OpBranchConditional %38 %40 %39
+         %40 = OpLabel
+         %41 = OpFunctionCall %void %tint_discard_func
+               OpReturnValue %7
+         %39 = OpLabel
+         %43 = OpIEqual %bool %36 %int_0
+               OpSelectionMerge %44 None
+               OpBranchConditional %43 %45 %44
+         %45 = OpLabel
+               OpBranch %33
+         %44 = OpLabel
+               OpBranch %34
+         %34 = OpLabel
+               OpBranch %32
+         %33 = OpLabel
+         %46 = OpLoad %int %y
+               OpReturnValue %46
+               OpFunctionEnd
+       %main = OpFunction %void None %21
+         %48 = OpLabel
+         %50 = OpLoad %v3int %x_1
+         %49 = OpFunctionCall %int %main_inner %50
+               OpStore %value %49
                OpReturn
                OpFunctionEnd
diff --git a/test/tint/bug/tint/1118.wgsl.expected.glsl b/test/tint/bug/tint/1118.wgsl.expected.glsl
index 7f61d30..a9bb098 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.glsl
+++ b/test/tint/bug/tint/1118.wgsl.expected.glsl
@@ -39,6 +39,7 @@
 } x_137;
 
 vec4 glFragColor = vec4(0.0f, 0.0f, 0.0f, 0.0f);
+bool tint_discard = false;
 void main_1() {
   vec3 viewDirectionW = vec3(0.0f, 0.0f, 0.0f);
   vec4 baseColor = vec4(0.0f, 0.0f, 0.0f, 0.0f);
@@ -57,10 +58,12 @@
   vec3 finalSpecular = vec3(0.0f, 0.0f, 0.0f);
   vec4 color = vec4(0.0f, 0.0f, 0.0f, 0.0f);
   if ((fClipDistance3 > 0.0f)) {
-    discard;
+    tint_discard = true;
+    return;
   }
   if ((fClipDistance4 > 0.0f)) {
-    discard;
+    tint_discard = true;
+    return;
   }
   vec4 x_34 = x_29.vEyePosition;
   vec3 x_38 = vec3(0.0f, 0.0f, 0.0f);
@@ -115,12 +118,24 @@
   fClipDistance3 = fClipDistance3_param;
   fClipDistance4 = fClipDistance4_param;
   main_1();
-  main_out tint_symbol_1 = main_out(glFragColor);
-  return tint_symbol_1;
+  if (tint_discard) {
+    main_out tint_symbol_1 = main_out(vec4(0.0f, 0.0f, 0.0f, 0.0f));
+    return tint_symbol_1;
+  }
+  main_out tint_symbol_2 = main_out(glFragColor);
+  return tint_symbol_2;
+}
+
+void tint_discard_func() {
+  discard;
 }
 
 void main() {
   main_out inner_result = tint_symbol(fClipDistance3_param_1, fClipDistance4_param_1);
+  if (tint_discard) {
+    tint_discard_func();
+    return;
+  }
   glFragColor_1_1 = inner_result.glFragColor_1;
   return;
 }
diff --git a/test/tint/bug/tint/1118.wgsl.expected.hlsl b/test/tint/bug/tint/1118.wgsl.expected.hlsl
index 9d86c0e..117a578 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.hlsl
+++ b/test/tint/bug/tint/1118.wgsl.expected.hlsl
@@ -1,5 +1,3 @@
-SKIP: FAILED
-
 static float fClipDistance3 = 0.0f;
 static float fClipDistance4 = 0.0f;
 cbuffer cbuffer_x_29 : register(b0, space0) {
@@ -12,6 +10,7 @@
   uint4 x_137[1];
 };
 static float4 glFragColor = float4(0.0f, 0.0f, 0.0f, 0.0f);
+static bool tint_discard = false;
 
 void main_1() {
   float3 viewDirectionW = float3(0.0f, 0.0f, 0.0f);
@@ -31,10 +30,12 @@
   float3 finalSpecular = float3(0.0f, 0.0f, 0.0f);
   float4 color = float4(0.0f, 0.0f, 0.0f, 0.0f);
   if ((fClipDistance3 > 0.0f)) {
-    discard;
+    tint_discard = true;
+    return;
   }
   if ((fClipDistance4 > 0.0f)) {
-    discard;
+    tint_discard = true;
+    return;
   }
   const float4 x_34 = asfloat(x_29[0]);
   const float3 x_38 = float3(0.0f, 0.0f, 0.0f);
@@ -96,15 +97,26 @@
   fClipDistance3 = fClipDistance3_param;
   fClipDistance4 = fClipDistance4_param;
   main_1();
-  const main_out tint_symbol_8 = {glFragColor};
-  return tint_symbol_8;
+  if (tint_discard) {
+    const main_out tint_symbol_8 = (main_out)0;
+    return tint_symbol_8;
+  }
+  const main_out tint_symbol_9 = {glFragColor};
+  return tint_symbol_9;
+}
+
+void tint_discard_func() {
+  discard;
 }
 
 tint_symbol_2 main(tint_symbol_1 tint_symbol) {
   const main_out inner_result = main_inner(tint_symbol.fClipDistance3_param, tint_symbol.fClipDistance4_param);
+  if (tint_discard) {
+    tint_discard_func();
+    const tint_symbol_2 tint_symbol_10 = (tint_symbol_2)0;
+    return tint_symbol_10;
+  }
   tint_symbol_2 wrapper_result = (tint_symbol_2)0;
   wrapper_result.glFragColor_1 = inner_result.glFragColor_1;
   return wrapper_result;
 }
-Internal error: unread predicate
-
diff --git a/test/tint/bug/tint/1118.wgsl.expected.msl b/test/tint/bug/tint/1118.wgsl.expected.msl
index 6f56a9c..e1bcabf 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.msl
+++ b/test/tint/bug/tint/1118.wgsl.expected.msl
@@ -28,7 +28,7 @@
   /* 0x0000 */ float visibility;
 };
 
-void main_1(thread float* const tint_symbol_5, thread float* const tint_symbol_6, const constant Scene* const tint_symbol_7, const constant Material* const tint_symbol_8, const constant Mesh* const tint_symbol_9, thread float4* const tint_symbol_10) {
+void main_1(thread float* const tint_symbol_7, thread bool* const tint_symbol_8, thread float* const tint_symbol_9, const constant Scene* const tint_symbol_10, const constant Material* const tint_symbol_11, const constant Mesh* const tint_symbol_12, thread float4* const tint_symbol_13) {
   float3 viewDirectionW = 0.0f;
   float4 baseColor = 0.0f;
   float3 diffuseColor = 0.0f;
@@ -45,21 +45,23 @@
   float3 finalDiffuse = 0.0f;
   float3 finalSpecular = 0.0f;
   float4 color = 0.0f;
-  float const x_9 = *(tint_symbol_5);
+  float const x_9 = *(tint_symbol_7);
   if ((x_9 > 0.0f)) {
-    discard_fragment();
+    *(tint_symbol_8) = true;
+    return;
   }
-  float const x_17 = *(tint_symbol_6);
+  float const x_17 = *(tint_symbol_9);
   if ((x_17 > 0.0f)) {
-    discard_fragment();
+    *(tint_symbol_8) = true;
+    return;
   }
-  float4 const x_34 = (*(tint_symbol_7)).vEyePosition;
+  float4 const x_34 = (*(tint_symbol_10)).vEyePosition;
   float3 const x_38 = float3(0.0f, 0.0f, 0.0f);
   viewDirectionW = normalize((float3(x_34[0], x_34[1], x_34[2]) - x_38));
   baseColor = float4(1.0f, 1.0f, 1.0f, 1.0f);
-  float4 const x_52 = (*(tint_symbol_8)).vDiffuseColor;
+  float4 const x_52 = (*(tint_symbol_11)).vDiffuseColor;
   diffuseColor = float3(x_52[0], x_52[1], x_52[2]);
-  float const x_60 = (*(tint_symbol_8)).vDiffuseColor[3];
+  float const x_60 = (*(tint_symbol_11)).vDiffuseColor[3];
   alpha = x_60;
   float3 const x_62 = float3(0.0f, 0.0f, 0.0f);
   float3 const x_64 = float3(0.0f, 0.0f, 0.0f);
@@ -76,12 +78,12 @@
   shadow = 1.0f;
   refractionColor = float4(0.0f, 0.0f, 0.0f, 1.0f);
   reflectionColor = float4(0.0f, 0.0f, 0.0f, 1.0f);
-  float3 const x_94 = (*(tint_symbol_8)).vEmissiveColor;
+  float3 const x_94 = (*(tint_symbol_11)).vEmissiveColor;
   emissiveColor = x_94;
   float3 const x_96 = diffuseBase;
   float3 const x_97 = diffuseColor;
   float3 const x_99 = emissiveColor;
-  float3 const x_103 = (*(tint_symbol_8)).vAmbientColor;
+  float3 const x_103 = (*(tint_symbol_11)).vAmbientColor;
   float4 const x_108 = baseColor;
   finalDiffuse = (clamp((((x_96 * x_97) + x_99) + x_103), float3(0.0f, 0.0f, 0.0f), float3(1.0f, 1.0f, 1.0f)) * float3(x_108[0], x_108[1], x_108[2]));
   finalSpecular = float3(0.0f, 0.0f, 0.0f);
@@ -97,11 +99,11 @@
   float3 const x_132 = fmax(float3(x_129[0], x_129[1], x_129[2]), float3(0.0f, 0.0f, 0.0f));
   float4 const x_133 = color;
   color = float4(x_132[0], x_132[1], x_132[2], x_133[3]);
-  float const x_140 = (*(tint_symbol_9)).visibility;
+  float const x_140 = (*(tint_symbol_12)).visibility;
   float const x_142 = color[3];
   color[3] = (x_142 * x_140);
   float4 const x_147 = color;
-  *(tint_symbol_10) = x_147;
+  *(tint_symbol_13) = x_147;
   return;
 }
 
@@ -118,19 +120,33 @@
   float4 glFragColor_1 [[color(0)]];
 };
 
-main_out tint_symbol_inner(float fClipDistance3_param, float fClipDistance4_param, thread float* const tint_symbol_11, thread float* const tint_symbol_12, const constant Scene* const tint_symbol_13, const constant Material* const tint_symbol_14, const constant Mesh* const tint_symbol_15, thread float4* const tint_symbol_16) {
-  *(tint_symbol_11) = fClipDistance3_param;
-  *(tint_symbol_12) = fClipDistance4_param;
-  main_1(tint_symbol_11, tint_symbol_12, tint_symbol_13, tint_symbol_14, tint_symbol_15, tint_symbol_16);
-  main_out const tint_symbol_4 = {.glFragColor_1=*(tint_symbol_16)};
-  return tint_symbol_4;
+main_out tint_symbol_inner(float fClipDistance3_param, float fClipDistance4_param, thread float* const tint_symbol_14, thread float* const tint_symbol_15, thread bool* const tint_symbol_16, const constant Scene* const tint_symbol_17, const constant Material* const tint_symbol_18, const constant Mesh* const tint_symbol_19, thread float4* const tint_symbol_20) {
+  *(tint_symbol_14) = fClipDistance3_param;
+  *(tint_symbol_15) = fClipDistance4_param;
+  main_1(tint_symbol_14, tint_symbol_16, tint_symbol_15, tint_symbol_17, tint_symbol_18, tint_symbol_19, tint_symbol_20);
+  if (*(tint_symbol_16)) {
+    main_out const tint_symbol_4 = {};
+    return tint_symbol_4;
+  }
+  main_out const tint_symbol_5 = {.glFragColor_1=*(tint_symbol_20)};
+  return tint_symbol_5;
 }
 
-fragment tint_symbol_3 tint_symbol(const constant Scene* tint_symbol_19 [[buffer(0)]], const constant Material* tint_symbol_20 [[buffer(1)]], const constant Mesh* tint_symbol_21 [[buffer(2)]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
-  thread float tint_symbol_17 = 0.0f;
-  thread float tint_symbol_18 = 0.0f;
-  thread float4 tint_symbol_22 = 0.0f;
-  main_out const inner_result = tint_symbol_inner(tint_symbol_1.fClipDistance3_param, tint_symbol_1.fClipDistance4_param, &(tint_symbol_17), &(tint_symbol_18), tint_symbol_19, tint_symbol_20, tint_symbol_21, &(tint_symbol_22));
+void tint_discard_func() {
+  discard_fragment();
+}
+
+fragment tint_symbol_3 tint_symbol(const constant Scene* tint_symbol_24 [[buffer(0)]], const constant Material* tint_symbol_25 [[buffer(1)]], const constant Mesh* tint_symbol_26 [[buffer(2)]], tint_symbol_2 tint_symbol_1 [[stage_in]]) {
+  thread float tint_symbol_21 = 0.0f;
+  thread float tint_symbol_22 = 0.0f;
+  thread bool tint_symbol_23 = false;
+  thread float4 tint_symbol_27 = 0.0f;
+  main_out const inner_result = tint_symbol_inner(tint_symbol_1.fClipDistance3_param, tint_symbol_1.fClipDistance4_param, &(tint_symbol_21), &(tint_symbol_22), &(tint_symbol_23), tint_symbol_24, tint_symbol_25, tint_symbol_26, &(tint_symbol_27));
+  if (tint_symbol_23) {
+    tint_discard_func();
+    tint_symbol_3 const tint_symbol_6 = {};
+    return tint_symbol_6;
+  }
   tint_symbol_3 wrapper_result = {};
   wrapper_result.glFragColor_1 = inner_result.glFragColor_1;
   return wrapper_result;
diff --git a/test/tint/bug/tint/1118.wgsl.expected.spvasm b/test/tint/bug/tint/1118.wgsl.expected.spvasm
index 390f103..3023204 100644
--- a/test/tint/bug/tint/1118.wgsl.expected.spvasm
+++ b/test/tint/bug/tint/1118.wgsl.expected.spvasm
@@ -1,10 +1,10 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 0
-; Bound: 187
+; Bound: 198
 ; Schema: 0
                OpCapability Shader
-         %69 = OpExtInstImport "GLSL.std.450"
+         %73 = OpExtInstImport "GLSL.std.450"
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %main "main" %fClipDistance3_param_1 %fClipDistance4_param_1 %glFragColor_1_1
                OpExecutionMode %main OriginUpperLeft
@@ -27,6 +27,7 @@
                OpMemberName %Mesh 0 "visibility"
                OpName %x_137 "x_137"
                OpName %glFragColor "glFragColor"
+               OpName %tint_discard "tint_discard"
                OpName %main_1 "main_1"
                OpName %viewDirectionW "viewDirectionW"
                OpName %baseColor "baseColor"
@@ -44,6 +45,7 @@
                OpName %finalDiffuse "finalDiffuse"
                OpName %finalSpecular "finalSpecular"
                OpName %color "color"
+               OpName %tint_discard_func "tint_discard_func"
                OpName %main_out "main_out"
                OpMemberName %main_out 0 "glFragColor_1"
                OpName %main_inner "main_inner"
@@ -97,199 +99,217 @@
       %x_137 = OpVariable %_ptr_Uniform_Mesh Uniform
 %_ptr_Private_v4float = OpTypePointer Private %v4float
 %glFragColor = OpVariable %_ptr_Private_v4float Private %8
+       %bool = OpTypeBool
+      %false = OpConstantFalse %bool
+%_ptr_Private_bool = OpTypePointer Private %bool
+%tint_discard = OpVariable %_ptr_Private_bool Private %false
        %void = OpTypeVoid
-         %25 = OpTypeFunction %void
+         %29 = OpTypeFunction %void
 %_ptr_Function_v3float = OpTypePointer Function %v3float
-         %31 = OpConstantNull %v3float
+         %35 = OpConstantNull %v3float
 %_ptr_Function_v4float = OpTypePointer Function %v4float
 %_ptr_Function_float = OpTypePointer Function %float
     %v2float = OpTypeVector %float 2
 %_ptr_Function_v2float = OpTypePointer Function %v2float
-         %41 = OpConstantNull %v2float
+         %45 = OpConstantNull %v2float
     %float_0 = OpConstant %float 0
-       %bool = OpTypeBool
+       %true = OpConstantTrue %bool
        %uint = OpTypeInt 32 0
      %uint_0 = OpConstant %uint 0
 %_ptr_Uniform_v4float = OpTypePointer Uniform %v4float
-         %67 = OpConstantComposite %v3float %float_0 %float_0 %float_0
+         %71 = OpConstantComposite %v3float %float_0 %float_0 %float_0
     %float_1 = OpConstant %float 1
-         %76 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
+         %80 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1
      %uint_3 = OpConstant %uint 3
 %_ptr_Uniform_float = OpTypePointer Uniform %float
-         %92 = OpConstantComposite %v2float %float_0 %float_0
-         %93 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
-        %110 = OpConstantComposite %v3float %float_1 %float_1 %float_1
-        %111 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
+         %96 = OpConstantComposite %v2float %float_0 %float_0
+         %97 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
+        %114 = OpConstantComposite %v3float %float_1 %float_1 %float_1
+        %115 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_1
 %_ptr_Uniform_v3float = OpTypePointer Uniform %v3float
      %uint_1 = OpConstant %uint 1
    %main_out = OpTypeStruct %v4float
-        %172 = OpTypeFunction %main_out %float %float
-     %main_1 = OpFunction %void None %25
-         %28 = OpLabel
-%viewDirectionW = OpVariable %_ptr_Function_v3float Function %31
+        %178 = OpTypeFunction %main_out %float %float
+        %189 = OpConstantNull %main_out
+     %main_1 = OpFunction %void None %29
+         %32 = OpLabel
+%viewDirectionW = OpVariable %_ptr_Function_v3float Function %35
   %baseColor = OpVariable %_ptr_Function_v4float Function %8
-%diffuseColor = OpVariable %_ptr_Function_v3float Function %31
+%diffuseColor = OpVariable %_ptr_Function_v3float Function %35
       %alpha = OpVariable %_ptr_Function_float Function %11
-    %normalW = OpVariable %_ptr_Function_v3float Function %31
-   %uvOffset = OpVariable %_ptr_Function_v2float Function %41
-%baseAmbientColor = OpVariable %_ptr_Function_v3float Function %31
+    %normalW = OpVariable %_ptr_Function_v3float Function %35
+   %uvOffset = OpVariable %_ptr_Function_v2float Function %45
+%baseAmbientColor = OpVariable %_ptr_Function_v3float Function %35
  %glossiness = OpVariable %_ptr_Function_float Function %11
-%diffuseBase = OpVariable %_ptr_Function_v3float Function %31
+%diffuseBase = OpVariable %_ptr_Function_v3float Function %35
      %shadow = OpVariable %_ptr_Function_float Function %11
 %refractionColor = OpVariable %_ptr_Function_v4float Function %8
 %reflectionColor = OpVariable %_ptr_Function_v4float Function %8
-%emissiveColor = OpVariable %_ptr_Function_v3float Function %31
-%finalDiffuse = OpVariable %_ptr_Function_v3float Function %31
-%finalSpecular = OpVariable %_ptr_Function_v3float Function %31
+%emissiveColor = OpVariable %_ptr_Function_v3float Function %35
+%finalDiffuse = OpVariable %_ptr_Function_v3float Function %35
+%finalSpecular = OpVariable %_ptr_Function_v3float Function %35
       %color = OpVariable %_ptr_Function_v4float Function %8
-         %52 = OpLoad %float %fClipDistance3
-         %54 = OpFOrdGreaterThan %bool %52 %float_0
-               OpSelectionMerge %56 None
-               OpBranchConditional %54 %57 %56
-         %57 = OpLabel
-               OpKill
-         %56 = OpLabel
-         %58 = OpLoad %float %fClipDistance4
-         %59 = OpFOrdGreaterThan %bool %58 %float_0
-               OpSelectionMerge %60 None
-               OpBranchConditional %59 %61 %60
-         %61 = OpLabel
-               OpKill
+         %56 = OpLoad %float %fClipDistance3
+         %58 = OpFOrdGreaterThan %bool %56 %float_0
+               OpSelectionMerge %59 None
+               OpBranchConditional %58 %60 %59
          %60 = OpLabel
-         %65 = OpAccessChain %_ptr_Uniform_v4float %x_29 %uint_0
-         %66 = OpLoad %v4float %65
-         %70 = OpCompositeExtract %float %66 0
-         %71 = OpCompositeExtract %float %66 1
-         %72 = OpCompositeExtract %float %66 2
-         %73 = OpCompositeConstruct %v3float %70 %71 %72
-         %74 = OpFSub %v3float %73 %67
-         %68 = OpExtInst %v3float %69 Normalize %74
-               OpStore %viewDirectionW %68
-               OpStore %baseColor %76
-         %77 = OpAccessChain %_ptr_Uniform_v4float %x_49 %uint_0
-         %78 = OpLoad %v4float %77
-         %79 = OpCompositeExtract %float %78 0
-         %80 = OpCompositeExtract %float %78 1
-         %81 = OpCompositeExtract %float %78 2
-         %82 = OpCompositeConstruct %v3float %79 %80 %81
-               OpStore %diffuseColor %82
-         %85 = OpAccessChain %_ptr_Uniform_float %x_49 %uint_0 %uint_3
-         %86 = OpLoad %float %85
-               OpStore %alpha %86
-         %90 = OpDPdx %v3float %67
-         %91 = OpDPdy %v3float %67
-         %89 = OpExtInst %v3float %69 Cross %90 %91
-         %88 = OpFNegate %v3float %89
-         %87 = OpExtInst %v3float %69 Normalize %88
-               OpStore %normalW %87
-               OpStore %uvOffset %92
-         %94 = OpLoad %v4float %baseColor
-         %95 = OpCompositeExtract %float %94 0
-         %96 = OpCompositeExtract %float %94 1
-         %97 = OpCompositeExtract %float %94 2
-         %98 = OpCompositeConstruct %v3float %95 %96 %97
-         %99 = OpCompositeExtract %float %93 0
-        %100 = OpCompositeExtract %float %93 1
-        %101 = OpCompositeExtract %float %93 2
+               OpStore %tint_discard %true
+               OpReturn
+         %59 = OpLabel
+         %62 = OpLoad %float %fClipDistance4
+         %63 = OpFOrdGreaterThan %bool %62 %float_0
+               OpSelectionMerge %64 None
+               OpBranchConditional %63 %65 %64
+         %65 = OpLabel
+               OpStore %tint_discard %true
+               OpReturn
+         %64 = OpLabel
+         %69 = OpAccessChain %_ptr_Uniform_v4float %x_29 %uint_0
+         %70 = OpLoad %v4float %69
+         %74 = OpCompositeExtract %float %70 0
+         %75 = OpCompositeExtract %float %70 1
+         %76 = OpCompositeExtract %float %70 2
+         %77 = OpCompositeConstruct %v3float %74 %75 %76
+         %78 = OpFSub %v3float %77 %71
+         %72 = OpExtInst %v3float %73 Normalize %78
+               OpStore %viewDirectionW %72
+               OpStore %baseColor %80
+         %81 = OpAccessChain %_ptr_Uniform_v4float %x_49 %uint_0
+         %82 = OpLoad %v4float %81
+         %83 = OpCompositeExtract %float %82 0
+         %84 = OpCompositeExtract %float %82 1
+         %85 = OpCompositeExtract %float %82 2
+         %86 = OpCompositeConstruct %v3float %83 %84 %85
+               OpStore %diffuseColor %86
+         %89 = OpAccessChain %_ptr_Uniform_float %x_49 %uint_0 %uint_3
+         %90 = OpLoad %float %89
+               OpStore %alpha %90
+         %94 = OpDPdx %v3float %71
+         %95 = OpDPdy %v3float %71
+         %93 = OpExtInst %v3float %73 Cross %94 %95
+         %92 = OpFNegate %v3float %93
+         %91 = OpExtInst %v3float %73 Normalize %92
+               OpStore %normalW %91
+               OpStore %uvOffset %96
+         %98 = OpLoad %v4float %baseColor
+         %99 = OpCompositeExtract %float %98 0
+        %100 = OpCompositeExtract %float %98 1
+        %101 = OpCompositeExtract %float %98 2
         %102 = OpCompositeConstruct %v3float %99 %100 %101
-        %103 = OpFMul %v3float %98 %102
-        %104 = OpLoad %v4float %baseColor
-        %105 = OpCompositeExtract %float %103 0
-        %106 = OpCompositeExtract %float %103 1
-        %107 = OpCompositeExtract %float %103 2
-        %108 = OpCompositeExtract %float %104 3
-        %109 = OpCompositeConstruct %v4float %105 %106 %107 %108
-               OpStore %baseColor %109
-               OpStore %baseAmbientColor %110
+        %103 = OpCompositeExtract %float %97 0
+        %104 = OpCompositeExtract %float %97 1
+        %105 = OpCompositeExtract %float %97 2
+        %106 = OpCompositeConstruct %v3float %103 %104 %105
+        %107 = OpFMul %v3float %102 %106
+        %108 = OpLoad %v4float %baseColor
+        %109 = OpCompositeExtract %float %107 0
+        %110 = OpCompositeExtract %float %107 1
+        %111 = OpCompositeExtract %float %107 2
+        %112 = OpCompositeExtract %float %108 3
+        %113 = OpCompositeConstruct %v4float %109 %110 %111 %112
+               OpStore %baseColor %113
+               OpStore %baseAmbientColor %114
                OpStore %glossiness %float_0
-               OpStore %diffuseBase %67
+               OpStore %diffuseBase %71
                OpStore %shadow %float_1
-               OpStore %refractionColor %111
-               OpStore %reflectionColor %111
-        %113 = OpAccessChain %_ptr_Uniform_v3float %x_49 %uint_3
-        %114 = OpLoad %v3float %113
-               OpStore %emissiveColor %114
-        %115 = OpLoad %v3float %diffuseBase
-        %116 = OpLoad %v3float %diffuseColor
-        %117 = OpLoad %v3float %emissiveColor
-        %119 = OpAccessChain %_ptr_Uniform_v3float %x_49 %uint_1
-        %120 = OpLoad %v3float %119
-        %121 = OpLoad %v4float %baseColor
-        %123 = OpFMul %v3float %115 %116
-        %124 = OpFAdd %v3float %123 %117
-        %125 = OpFAdd %v3float %124 %120
-        %122 = OpExtInst %v3float %69 NClamp %125 %67 %110
-        %126 = OpCompositeExtract %float %121 0
-        %127 = OpCompositeExtract %float %121 1
-        %128 = OpCompositeExtract %float %121 2
-        %129 = OpCompositeConstruct %v3float %126 %127 %128
-        %130 = OpFMul %v3float %122 %129
-               OpStore %finalDiffuse %130
-               OpStore %finalSpecular %67
-        %131 = OpLoad %v3float %finalDiffuse
-        %132 = OpLoad %v3float %baseAmbientColor
-        %133 = OpLoad %v3float %finalSpecular
-        %134 = OpLoad %v4float %reflectionColor
-        %135 = OpLoad %v4float %refractionColor
-        %136 = OpFMul %v3float %131 %132
-        %137 = OpFAdd %v3float %136 %133
-        %138 = OpCompositeExtract %float %134 0
-        %139 = OpCompositeExtract %float %134 1
-        %140 = OpCompositeExtract %float %134 2
-        %141 = OpCompositeConstruct %v3float %138 %139 %140
-        %142 = OpFAdd %v3float %137 %141
-        %143 = OpCompositeExtract %float %135 0
-        %144 = OpCompositeExtract %float %135 1
-        %145 = OpCompositeExtract %float %135 2
-        %146 = OpCompositeConstruct %v3float %143 %144 %145
-        %147 = OpFAdd %v3float %142 %146
-        %148 = OpLoad %float %alpha
-        %149 = OpCompositeExtract %float %147 0
-        %150 = OpCompositeExtract %float %147 1
-        %151 = OpCompositeExtract %float %147 2
-        %152 = OpCompositeConstruct %v4float %149 %150 %151 %148
-               OpStore %color %152
-        %153 = OpLoad %v4float %color
-        %155 = OpCompositeExtract %float %153 0
-        %156 = OpCompositeExtract %float %153 1
-        %157 = OpCompositeExtract %float %153 2
-        %158 = OpCompositeConstruct %v3float %155 %156 %157
-        %154 = OpExtInst %v3float %69 NMax %158 %67
-        %159 = OpLoad %v4float %color
-        %160 = OpCompositeExtract %float %154 0
-        %161 = OpCompositeExtract %float %154 1
-        %162 = OpCompositeExtract %float %154 2
-        %163 = OpCompositeExtract %float %159 3
-        %164 = OpCompositeConstruct %v4float %160 %161 %162 %163
-               OpStore %color %164
-        %165 = OpAccessChain %_ptr_Uniform_float %x_137 %uint_0
-        %166 = OpLoad %float %165
-        %167 = OpAccessChain %_ptr_Function_float %color %uint_3
-        %168 = OpLoad %float %167
-        %169 = OpAccessChain %_ptr_Function_float %color %uint_3
-        %170 = OpFMul %float %168 %166
-               OpStore %169 %170
-        %171 = OpLoad %v4float %color
-               OpStore %glFragColor %171
+               OpStore %refractionColor %115
+               OpStore %reflectionColor %115
+        %117 = OpAccessChain %_ptr_Uniform_v3float %x_49 %uint_3
+        %118 = OpLoad %v3float %117
+               OpStore %emissiveColor %118
+        %119 = OpLoad %v3float %diffuseBase
+        %120 = OpLoad %v3float %diffuseColor
+        %121 = OpLoad %v3float %emissiveColor
+        %123 = OpAccessChain %_ptr_Uniform_v3float %x_49 %uint_1
+        %124 = OpLoad %v3float %123
+        %125 = OpLoad %v4float %baseColor
+        %127 = OpFMul %v3float %119 %120
+        %128 = OpFAdd %v3float %127 %121
+        %129 = OpFAdd %v3float %128 %124
+        %126 = OpExtInst %v3float %73 NClamp %129 %71 %114
+        %130 = OpCompositeExtract %float %125 0
+        %131 = OpCompositeExtract %float %125 1
+        %132 = OpCompositeExtract %float %125 2
+        %133 = OpCompositeConstruct %v3float %130 %131 %132
+        %134 = OpFMul %v3float %126 %133
+               OpStore %finalDiffuse %134
+               OpStore %finalSpecular %71
+        %135 = OpLoad %v3float %finalDiffuse
+        %136 = OpLoad %v3float %baseAmbientColor
+        %137 = OpLoad %v3float %finalSpecular
+        %138 = OpLoad %v4float %reflectionColor
+        %139 = OpLoad %v4float %refractionColor
+        %140 = OpFMul %v3float %135 %136
+        %141 = OpFAdd %v3float %140 %137
+        %142 = OpCompositeExtract %float %138 0
+        %143 = OpCompositeExtract %float %138 1
+        %144 = OpCompositeExtract %float %138 2
+        %145 = OpCompositeConstruct %v3float %142 %143 %144
+        %146 = OpFAdd %v3float %141 %145
+        %147 = OpCompositeExtract %float %139 0
+        %148 = OpCompositeExtract %float %139 1
+        %149 = OpCompositeExtract %float %139 2
+        %150 = OpCompositeConstruct %v3float %147 %148 %149
+        %151 = OpFAdd %v3float %146 %150
+        %152 = OpLoad %float %alpha
+        %153 = OpCompositeExtract %float %151 0
+        %154 = OpCompositeExtract %float %151 1
+        %155 = OpCompositeExtract %float %151 2
+        %156 = OpCompositeConstruct %v4float %153 %154 %155 %152
+               OpStore %color %156
+        %157 = OpLoad %v4float %color
+        %159 = OpCompositeExtract %float %157 0
+        %160 = OpCompositeExtract %float %157 1
+        %161 = OpCompositeExtract %float %157 2
+        %162 = OpCompositeConstruct %v3float %159 %160 %161
+        %158 = OpExtInst %v3float %73 NMax %162 %71
+        %163 = OpLoad %v4float %color
+        %164 = OpCompositeExtract %float %158 0
+        %165 = OpCompositeExtract %float %158 1
+        %166 = OpCompositeExtract %float %158 2
+        %167 = OpCompositeExtract %float %163 3
+        %168 = OpCompositeConstruct %v4float %164 %165 %166 %167
+               OpStore %color %168
+        %169 = OpAccessChain %_ptr_Uniform_float %x_137 %uint_0
+        %170 = OpLoad %float %169
+        %171 = OpAccessChain %_ptr_Function_float %color %uint_3
+        %172 = OpLoad %float %171
+        %173 = OpAccessChain %_ptr_Function_float %color %uint_3
+        %174 = OpFMul %float %172 %170
+               OpStore %173 %174
+        %175 = OpLoad %v4float %color
+               OpStore %glFragColor %175
                OpReturn
                OpFunctionEnd
- %main_inner = OpFunction %main_out None %172
+%tint_discard_func = OpFunction %void None %29
+        %177 = OpLabel
+               OpKill
+               OpFunctionEnd
+ %main_inner = OpFunction %main_out None %178
 %fClipDistance3_param = OpFunctionParameter %float
 %fClipDistance4_param = OpFunctionParameter %float
-        %177 = OpLabel
+        %183 = OpLabel
                OpStore %fClipDistance3 %fClipDistance3_param
                OpStore %fClipDistance4 %fClipDistance4_param
-        %178 = OpFunctionCall %void %main_1
-        %179 = OpLoad %v4float %glFragColor
-        %180 = OpCompositeConstruct %main_out %179
-               OpReturnValue %180
+        %184 = OpFunctionCall %void %main_1
+        %185 = OpLoad %bool %tint_discard
+               OpSelectionMerge %186 None
+               OpBranchConditional %185 %187 %186
+        %187 = OpLabel
+        %188 = OpFunctionCall %void %tint_discard_func
+               OpReturnValue %189
+        %186 = OpLabel
+        %190 = OpLoad %v4float %glFragColor
+        %191 = OpCompositeConstruct %main_out %190
+               OpReturnValue %191
                OpFunctionEnd
-       %main = OpFunction %void None %25
-        %182 = OpLabel
-        %184 = OpLoad %float %fClipDistance3_param_1
-        %185 = OpLoad %float %fClipDistance4_param_1
-        %183 = OpFunctionCall %main_out %main_inner %184 %185
-        %186 = OpCompositeExtract %v4float %183 0
-               OpStore %glFragColor_1_1 %186
+       %main = OpFunction %void None %29
+        %193 = OpLabel
+        %195 = OpLoad %float %fClipDistance3_param_1
+        %196 = OpLoad %float %fClipDistance4_param_1
+        %194 = OpFunctionCall %main_out %main_inner %195 %196
+        %197 = OpCompositeExtract %v4float %194 0
+               OpStore %glFragColor_1_1 %197
                OpReturn
                OpFunctionEnd
diff --git a/test/tint/bug/tint/1369.wgsl.expected.glsl b/test/tint/bug/tint/1369.wgsl.expected.glsl
index 4d0b9b7..dbecc85 100644
--- a/test/tint/bug/tint/1369.wgsl.expected.glsl
+++ b/test/tint/bug/tint/1369.wgsl.expected.glsl
@@ -9,17 +9,30 @@
 #version 310 es
 precision mediump float;
 
+bool tint_discard = false;
 bool call_discard() {
-  discard;
+  tint_discard = true;
+  return false;
   return true;
 }
 
 void f() {
   bool v = call_discard();
+  if (tint_discard) {
+    return;
+  }
   bool also_unreachable = false;
 }
 
+void tint_discard_func() {
+  discard;
+}
+
 void main() {
   f();
+  if (tint_discard) {
+    tint_discard_func();
+    return;
+  }
   return;
 }
diff --git a/test/tint/bug/tint/1369.wgsl.expected.hlsl b/test/tint/bug/tint/1369.wgsl.expected.hlsl
index dff55b3..d0fffc5 100644
--- a/test/tint/bug/tint/1369.wgsl.expected.hlsl
+++ b/test/tint/bug/tint/1369.wgsl.expected.hlsl
@@ -6,17 +6,24 @@
     var also_unreachable : bool;
         ^^^^^^^^^^^^^^^^
 
+static bool tint_discard = false;
+
 bool call_discard() {
-  if (true) {
-    discard;
-    return true;
-  }
-  bool unused;
-  return unused;
+  tint_discard = true;
+  return false;
+  return true;
+}
+
+void tint_discard_func() {
+  discard;
 }
 
 void f() {
   bool v = call_discard();
+  if (tint_discard) {
+    tint_discard_func();
+    return;
+  }
   bool also_unreachable = false;
   return;
 }
diff --git a/test/tint/bug/tint/1369.wgsl.expected.msl b/test/tint/bug/tint/1369.wgsl.expected.msl
index 8d1a7d7..e5ce396 100644
--- a/test/tint/bug/tint/1369.wgsl.expected.msl
+++ b/test/tint/bug/tint/1369.wgsl.expected.msl
@@ -9,13 +9,23 @@
 #include <metal_stdlib>
 
 using namespace metal;
-bool call_discard() {
-  discard_fragment();
+bool call_discard(thread bool* const tint_symbol) {
+  *(tint_symbol) = true;
+  return bool();
   return true;
 }
 
+void tint_discard_func() {
+  discard_fragment();
+}
+
 fragment void f() {
-  bool v = call_discard();
+  thread bool tint_symbol_1 = false;
+  bool v = call_discard(&(tint_symbol_1));
+  if (tint_symbol_1) {
+    tint_discard_func();
+    return;
+  }
   bool also_unreachable = false;
   return;
 }
diff --git a/test/tint/bug/tint/1369.wgsl.expected.spvasm b/test/tint/bug/tint/1369.wgsl.expected.spvasm
index a78ae04..e3ece9c 100644
--- a/test/tint/bug/tint/1369.wgsl.expected.spvasm
+++ b/test/tint/bug/tint/1369.wgsl.expected.spvasm
@@ -9,29 +9,47 @@
 ; SPIR-V
 ; Version: 1.3
 ; Generator: Google Tint Compiler; 0
-; Bound: 13
+; Bound: 23
 ; Schema: 0
                OpCapability Shader
                OpMemoryModel Logical GLSL450
                OpEntryPoint Fragment %f "f"
                OpExecutionMode %f OriginUpperLeft
+               OpName %tint_discard "tint_discard"
                OpName %call_discard "call_discard"
+               OpName %tint_discard_func "tint_discard_func"
                OpName %f "f"
                OpName %v "v"
        %bool = OpTypeBool
-          %1 = OpTypeFunction %bool
+      %false = OpConstantFalse %bool
+%_ptr_Private_bool = OpTypePointer Private %bool
+%tint_discard = OpVariable %_ptr_Private_bool Private %false
+          %5 = OpTypeFunction %bool
+       %true = OpConstantTrue %bool
+          %9 = OpConstantNull %bool
        %void = OpTypeVoid
-          %5 = OpTypeFunction %void
+         %10 = OpTypeFunction %void
 %_ptr_Function_bool = OpTypePointer Function %bool
-         %12 = OpConstantNull %bool
-%call_discard = OpFunction %bool None %1
-          %4 = OpLabel
+%call_discard = OpFunction %bool None %5
+          %7 = OpLabel
+               OpStore %tint_discard %true
+               OpReturnValue %9
+               OpFunctionEnd
+%tint_discard_func = OpFunction %void None %10
+         %13 = OpLabel
                OpKill
                OpFunctionEnd
-          %f = OpFunction %void None %5
-          %8 = OpLabel
-          %v = OpVariable %_ptr_Function_bool Function %12
-          %9 = OpFunctionCall %bool %call_discard
-               OpStore %v %9
+          %f = OpFunction %void None %10
+         %15 = OpLabel
+          %v = OpVariable %_ptr_Function_bool Function %9
+         %16 = OpFunctionCall %bool %call_discard
+               OpStore %v %16
+         %19 = OpLoad %bool %tint_discard
+               OpSelectionMerge %20 None
+               OpBranchConditional %19 %21 %20
+         %21 = OpLabel
+         %22 = OpFunctionCall %void %tint_discard_func
+               OpReturn
+         %20 = OpLabel
                OpReturn
                OpFunctionEnd
diff --git a/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm.expected.hlsl b/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm.expected.hlsl
deleted file mode 100644
index cc96aff..0000000
--- a/test/tint/unittest/reader/spirv/SpvParserCFGTest_EmitBody_Kill_InsideLoop.spvasm.expected.hlsl
+++ /dev/null
@@ -1,17 +0,0 @@
-SKIP: FAILED
-
-warning: code is unreachable
-static uint var_1 = 0u;
-
-void main_1() {
-  [loop] while (true) {
-    discard;
-  }
-  discard;
-}
-
-void main() {
-  main_1();
-  return;
-}
-
diff --git a/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm.expected.hlsl
deleted file mode 100644
index 1db4c8c..0000000
--- a/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.spvasm.expected.hlsl
+++ /dev/null
@@ -1,50 +0,0 @@
-SKIP: FAILED
-
-cbuffer cbuffer_x_6 : register(b0, space0) {
-  uint4 x_6[1];
-};
-static float4 x_GLF_color = float4(0.0f, 0.0f, 0.0f, 0.0f);
-
-void func_() {
-  const float x_28 = asfloat(x_6[0].x);
-  if ((1.0f > x_28)) {
-    discard;
-  }
-  return;
-}
-
-void main_1() {
-  x_GLF_color = float4(0.0f, 0.0f, 0.0f, 0.0f);
-  [loop] while (true) {
-    func_();
-    if (false) {
-    } else {
-      break;
-    }
-  }
-  x_GLF_color = float4(1.0f, 0.0f, 0.0f, 1.0f);
-  return;
-}
-
-struct main_out {
-  float4 x_GLF_color_1;
-};
-struct tint_symbol {
-  float4 x_GLF_color_1 : SV_Target0;
-};
-
-main_out main_inner() {
-  main_1();
-  const main_out tint_symbol_2 = {x_GLF_color};
-  return tint_symbol_2;
-}
-
-tint_symbol main() {
-  const main_out inner_result = main_inner();
-  tint_symbol wrapper_result = (tint_symbol)0;
-  wrapper_result.x_GLF_color_1 = inner_result.x_GLF_color_1;
-  return wrapper_result;
-}
-C:\src\tint\test\Shader@0x0000016102DC2B30(16,10-21): warning X3557: loop only executes for 0 iteration(s), consider removing [loop]
-C:\src\tint\test\Shader@0x0000016102DC2B30(8,15-18): internal error: invalid access of unbound variable
-
diff --git a/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl.expected.hlsl
deleted file mode 100644
index a528cf5..0000000
--- a/test/tint/vk-gl-cts/graphicsfuzz/call-function-with-discard/0-opt.wgsl.expected.hlsl
+++ /dev/null
@@ -1,50 +0,0 @@
-SKIP: FAILED
-
-cbuffer cbuffer_x_6 : register(b0, space0) {
-  uint4 x_6[1];
-};
-static float4 x_GLF_color = float4(0.0f, 0.0f, 0.0f, 0.0f);
-
-void func_() {
-  const float x_28 = asfloat(x_6[0].x);
-  if ((1.0f > x_28)) {
-    discard;
-  }
-  return;
-}
-
-void main_1() {
-  x_GLF_color = float4(0.0f, 0.0f, 0.0f, 0.0f);
-  [loop] while (true) {
-    func_();
-    if (false) {
-    } else {
-      break;
-    }
-  }
-  x_GLF_color = float4(1.0f, 0.0f, 0.0f, 1.0f);
-  return;
-}
-
-struct main_out {
-  float4 x_GLF_color_1;
-};
-struct tint_symbol {
-  float4 x_GLF_color_1 : SV_Target0;
-};
-
-main_out main_inner() {
-  main_1();
-  const main_out tint_symbol_2 = {x_GLF_color};
-  return tint_symbol_2;
-}
-
-tint_symbol main() {
-  const main_out inner_result = main_inner();
-  tint_symbol wrapper_result = (tint_symbol)0;
-  wrapper_result.x_GLF_color_1 = inner_result.x_GLF_color_1;
-  return wrapper_result;
-}
-C:\src\tint\test\Shader@0x0000026A3937ED10(16,10-21): warning X3557: loop only executes for 0 iteration(s), consider removing [loop]
-C:\src\tint\test\Shader@0x0000026A3937ED10(8,15-18): internal error: invalid access of unbound variable
-
diff --git a/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm.expected.hlsl
deleted file mode 100755
index 1626926..0000000
--- a/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.spvasm.expected.hlsl
+++ /dev/null
Binary files differ
diff --git a/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl.expected.hlsl
deleted file mode 100755
index 711e15f..0000000
--- a/test/tint/vk-gl-cts/graphicsfuzz/stable-binarysearch-tree-false-if-discard-loop/1.wgsl.expected.hlsl
+++ /dev/null
Binary files differ
diff --git a/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm.expected.hlsl
deleted file mode 100644
index 1f1e152..0000000
--- a/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.spvasm.expected.hlsl
+++ /dev/null
@@ -1,64 +0,0 @@
-SKIP: FAILED
-
-cbuffer cbuffer_x_7 : register(b0, space0) {
-  uint4 x_7[1];
-};
-static float4 gl_FragCoord = float4(0.0f, 0.0f, 0.0f, 0.0f);
-static float4 x_GLF_color = float4(0.0f, 0.0f, 0.0f, 0.0f);
-
-float3 computePoint_() {
-  if (true) {
-    const float x_48 = asfloat(x_7[0].x);
-    const float x_50 = asfloat(x_7[0].y);
-    if ((x_48 > x_50)) {
-      discard;
-    }
-    return float3(0.0f, 0.0f, 0.0f);
-  }
-  float3 unused;
-  return unused;
-}
-
-void main_1() {
-  bool x_34 = false;
-  [loop] while (true) {
-    const float3 x_36 = computePoint_();
-    const float x_41 = gl_FragCoord.x;
-    if ((x_41 < 0.0f)) {
-      x_34 = true;
-      break;
-    }
-    const float3 x_45 = computePoint_();
-    x_GLF_color = float4(1.0f, 0.0f, 0.0f, 1.0f);
-    x_34 = true;
-    break;
-  }
-  return;
-}
-
-struct main_out {
-  float4 x_GLF_color_1;
-};
-struct tint_symbol_1 {
-  float4 gl_FragCoord_param : SV_Position;
-};
-struct tint_symbol_2 {
-  float4 x_GLF_color_1 : SV_Target0;
-};
-
-main_out main_inner(float4 gl_FragCoord_param) {
-  gl_FragCoord = gl_FragCoord_param;
-  main_1();
-  const main_out tint_symbol_4 = {x_GLF_color};
-  return tint_symbol_4;
-}
-
-tint_symbol_2 main(tint_symbol_1 tint_symbol) {
-  const main_out inner_result = main_inner(tint_symbol.gl_FragCoord_param);
-  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
-  wrapper_result.x_GLF_color_1 = inner_result.x_GLF_color_1;
-  return wrapper_result;
-}
-C:\src\tint\test\Shader@0x0000014F8C58FE70(22,10-21): warning X3557: loop only executes for 0 iteration(s), consider removing [loop]
-C:\src\tint\test\Shader@0x0000014F8C58FE70(11,10-13): internal error: invalid access of unbound variable
-
diff --git a/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl.expected.hlsl b/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl.expected.hlsl
deleted file mode 100644
index 1bbf048..0000000
--- a/test/tint/vk-gl-cts/graphicsfuzz/unreachable-discard-statement-in-if/0-opt.wgsl.expected.hlsl
+++ /dev/null
@@ -1,64 +0,0 @@
-SKIP: FAILED
-
-cbuffer cbuffer_x_7 : register(b0, space0) {
-  uint4 x_7[1];
-};
-static float4 gl_FragCoord = float4(0.0f, 0.0f, 0.0f, 0.0f);
-static float4 x_GLF_color = float4(0.0f, 0.0f, 0.0f, 0.0f);
-
-float3 computePoint_() {
-  if (true) {
-    const float x_48 = asfloat(x_7[0].x);
-    const float x_50 = asfloat(x_7[0].y);
-    if ((x_48 > x_50)) {
-      discard;
-    }
-    return float3(0.0f, 0.0f, 0.0f);
-  }
-  float3 unused;
-  return unused;
-}
-
-void main_1() {
-  bool x_34 = false;
-  [loop] while (true) {
-    const float3 x_36 = computePoint_();
-    const float x_41 = gl_FragCoord.x;
-    if ((x_41 < 0.0f)) {
-      x_34 = true;
-      break;
-    }
-    const float3 x_45 = computePoint_();
-    x_GLF_color = float4(1.0f, 0.0f, 0.0f, 1.0f);
-    x_34 = true;
-    break;
-  }
-  return;
-}
-
-struct main_out {
-  float4 x_GLF_color_1;
-};
-struct tint_symbol_1 {
-  float4 gl_FragCoord_param : SV_Position;
-};
-struct tint_symbol_2 {
-  float4 x_GLF_color_1 : SV_Target0;
-};
-
-main_out main_inner(float4 gl_FragCoord_param) {
-  gl_FragCoord = gl_FragCoord_param;
-  main_1();
-  const main_out tint_symbol_4 = {x_GLF_color};
-  return tint_symbol_4;
-}
-
-tint_symbol_2 main(tint_symbol_1 tint_symbol) {
-  const main_out inner_result = main_inner(tint_symbol.gl_FragCoord_param);
-  tint_symbol_2 wrapper_result = (tint_symbol_2)0;
-  wrapper_result.x_GLF_color_1 = inner_result.x_GLF_color_1;
-  return wrapper_result;
-}
-C:\src\tint\test\Shader@0x0000017E3AA920C0(22,10-21): warning X3557: loop only executes for 0 iteration(s), consider removing [loop]
-C:\src\tint\test\Shader@0x0000017E3AA920C0(11,10-13): internal error: invalid access of unbound variable
-