[ir] When raising to a program handle unreachable as function terminator.

If an unreachable is the terminator of a function, and the function is
non-void, we swap the terminator out for a `return` of the function
return types zero value. This works around an issue where WGSL requires
a terminator, but does not have the behaviour analysis to determine if
the end of function is actually reachable or not.

Bug: 42250952
Change-Id: I5ef07147632d3ac54fdcc911cc844b959861626b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/246075
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/wgsl/ir_roundtrip_test.cc b/src/tint/lang/wgsl/ir_roundtrip_test.cc
index a829ee7..9a38177 100644
--- a/src/tint/lang/wgsl/ir_roundtrip_test.cc
+++ b/src/tint/lang/wgsl/ir_roundtrip_test.cc
@@ -2318,6 +2318,17 @@
     return 2.0f;
   }
 }
+)",
+             R"(
+fn f() -> f32 {
+  var cond : bool = true;
+  if (cond) {
+    return 1.0f;
+  } else {
+    return 2.0f;
+  }
+  return f32();
+}
 )");
 }
 
@@ -2639,6 +2650,7 @@
       }
     }
   }
+  return i32();
 }
 )");
 }
@@ -3351,6 +3363,24 @@
     }
   }
 }
+)",
+             R"(
+fn f() -> i32 {
+  var i : i32;
+  switch(i) {
+    case 0i: {
+      return i;
+    }
+    case 1i: {
+      var i_1 : i32 = (i + 1i);
+      return i_1;
+    }
+    default: {
+      return i;
+    }
+  }
+  return i32();
+}
 )");
 }
 
@@ -3371,6 +3401,24 @@
     }
   }
 }
+)",
+             R"(
+fn f() -> i32 {
+  var i : i32;
+  switch(i) {
+    case 0i: {
+      return i;
+    }
+    case 1i: {
+      let i_1 = (i + 1i);
+      return i_1;
+    }
+    default: {
+      return i;
+    }
+  }
+  return i32();
+}
 )");
 }
 
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
index 333781c..05e0edf 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
@@ -179,6 +179,9 @@
     /// Map of struct to output program name.
     Hashmap<const core::type::Struct*, Symbol, 8> structs_;
 
+    /// The current function being emitted.
+    const core::ir::Function* current_function_ = nullptr;
+
     /// True if 'diagnostic(off, derivative_uniformity)' has been emitted
     bool disabled_derivative_uniformity_ = false;
 
@@ -194,6 +197,7 @@
     }
     const ast::Function* Fn(const core::ir::Function* fn) {
         TINT_SCOPED_ASSIGNMENT(nesting_depth_, nesting_depth_ + 1);
+        TINT_SCOPED_ASSIGNMENT(current_function_, fn);
 
         // Emit parameters.
         static constexpr size_t N = decltype(ast::Function::params)::static_length;
@@ -368,11 +372,26 @@
             [&](const core::ir::Switch* i) { Switch(i); },                          //
             [&](const core::ir::Swizzle* i) { Swizzle(i); },                        //
             [&](const core::ir::Unary* i) { Unary(i); },                            //
-            [&](const core::ir::Unreachable*) {},                                   //
+            [&](const core::ir::Unreachable* u) { Unreachable(u); },                //
             [&](const core::ir::Var* i) { Var(i); },                                //
             TINT_ICE_ON_NO_MATCH);
     }
 
+    // In the case of an `unreachable` as the last statement in a non-void function, swap it to a
+    // `return` of the zero value for the return type. This is to satisfy the requirement for WGSL
+    // to always end in a `return` but `unreachable` really only meaning undefined behaviour if you
+    // get here.
+    void Unreachable(const core::ir::Unreachable* u) {
+        if (current_function_->ReturnType()->Is<core::type::Void>()) {
+            return;
+        }
+        if (u != current_function_->Block()->Terminator()) {
+            return;
+        }
+
+        Append(b.Return(b.Call(Type(current_function_->ReturnType()))));
+    }
+
     void If(const core::ir::If* if_) {
         TINT_SCOPED_ASSIGNMENT(nesting_depth_, nesting_depth_ + 1);
 
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
index 6ce4c2d..9673652 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
@@ -95,6 +95,18 @@
 )");
 }
 
+TEST_F(IRToProgramTest, SingleFunction_Unreachable) {
+    auto* fn = b.Function("f", ty.u32());
+
+    fn->Block()->Append(b.Unreachable());
+
+    EXPECT_WGSL(R"(
+fn f() -> u32 {
+  return u32();
+}
+)");
+}
+
 TEST_F(IRToProgramTest, SingleFunction_Return_i32) {
     auto* fn = b.Function("f", ty.i32());
 
@@ -2187,6 +2199,7 @@
   } else {
     return 2.0f;
   }
+  return f32();
 }
 )");
 }