tint: Add implicit CF_return->{last cf} edge

If the function has no return statements, we need to do this to
capture the potential non-uniformity coming from nested function
calls.

This also removes the need to add explicit edges for discard
statements, so remove them.

Bug: tint:880
Change-Id: I88b1132faf35a6d36460ef353912f77a15f8abaa
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/89861
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index 33def3b..770e6ee 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -314,7 +314,8 @@
 
         // Process function body.
         if (func->body) {
-            ProcessStatement(current_function_->cf_start, func->body);
+            auto* cf = ProcessStatement(current_function_->cf_start, func->body);
+            current_function_->cf_return->AddEdge(cf);
         }
 
 #if TINT_DUMP_UNIFORMITY_GRAPH
@@ -556,10 +557,7 @@
                 return cf;
             },
 
-            [&](const ast::DiscardStatement*) {
-                current_function_->cf_return->AddEdge(cf);
-                return cf;
-            },
+            [&](const ast::DiscardStatement*) { return cf; },
 
             [&](const ast::FallthroughStatement*) { return cf; },
 
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index 77a9dfe..037f573 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -401,6 +401,47 @@
 )");
 }
 
+TEST_F(UniformityAnalysisTest, SubsequentControlFlowMayBeNonUniform_Nested_Fail) {
+    // Indirectly call a function that causes subsequent control flow to be non-uniform, and then
+    // call another function that requires uniformity.
+    // The lack of return statement in `foo()` requires that we implicitly add an edge from
+    // CF_return to that last control flow node of the function.
+    auto src = R"(
+@group(0) @binding(0) var<storage, read_write> rw : i32;
+
+var<private> p : i32;
+
+fn bar() {
+  if (rw == 0) {
+    p = 42;
+    return;
+  }
+  p = 5;
+  return;
+}
+
+fn foo() {
+  bar();
+}
+
+fn main() {
+  foo();
+  workgroupBarrier();
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:21:3 warning: 'workgroupBarrier' must only be called from uniform control flow
+  workgroupBarrier();
+  ^^^^^^^^^^^^^^^^
+
+test:20:3 note: calling 'foo' may cause subsequent control flow to be non-uniform
+  foo();
+  ^^^
+)");
+}
+
 TEST_F(UniformityAnalysisTest, ParameterNoRestriction_Pass) {
     // Pass a non-uniform value as an argument, and then try to use the return value for
     // control-flow guarding a barrier.