Check that if A calls B, B is defined before A for WebGPU (#2169)

Fixes #2067
diff --git a/source/val/validate.cpp b/source/val/validate.cpp
index 5236e14..abc2f31 100644
--- a/source/val/validate.cpp
+++ b/source/val/validate.cpp
@@ -279,7 +279,15 @@
                  << "A FunctionCall must happen within a function body.";
         }
 
-        vstate->AddFunctionCallTarget(inst->GetOperandAs<uint32_t>(2));
+        const auto called_id = inst->GetOperandAs<uint32_t>(2);
+        if (spvIsWebGPUEnv(context.target_env) &&
+            !vstate->IsFunctionCallDefined(called_id)) {
+          return vstate->diag(SPV_ERROR_INVALID_LAYOUT, &instruction)
+                 << "For WebGPU, functions need to be defined before being "
+                    "called.";
+        }
+
+        vstate->AddFunctionCallTarget(called_id);
       }
 
       if (vstate->in_function_body()) {
diff --git a/source/val/validation_state.h b/source/val/validation_state.h
index 50f5812..cbd9a34 100644
--- a/source/val/validation_state.h
+++ b/source/val/validation_state.h
@@ -280,6 +280,9 @@
     return (function_call_targets_.find(id) != function_call_targets_.end());
   }
 
+  bool IsFunctionCallDefined(const uint32_t id) {
+    return (id_to_function_.find(id) != id_to_function_.end());
+  }
   /// Registers the capability and its dependent capabilities
   void RegisterCapability(SpvCapability cap);
 
diff --git a/test/val/val_layout_test.cpp b/test/val/val_layout_test.cpp
index e67c840..e502d8c 100644
--- a/test/val/val_layout_test.cpp
+++ b/test/val/val_layout_test.cpp
@@ -648,6 +648,57 @@
       HasSubstr("ModuleProcessed cannot appear in a function declaration"));
 }
 
+TEST_F(ValidateLayout, WebGPUCallerBeforeCalleeBad) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability VulkanMemoryModelKHR
+           OpExtension "SPV_KHR_vulkan_memory_model"
+           OpMemoryModel Logical VulkanKHR
+           OpEntryPoint GLCompute %main "main"
+%void    = OpTypeVoid
+%voidfn  = OpTypeFunction %void
+%main    = OpFunction %void None %voidfn
+%1       = OpLabel
+%2       = OpFunctionCall %void %callee
+           OpReturn
+           OpFunctionEnd
+%callee  = OpFunction %void None %voidfn
+%3       = OpLabel
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, functions need to be defined before being "
+                        "called.\n  %5 = OpFunctionCall %void %6\n"));
+}
+
+TEST_F(ValidateLayout, WebGPUCalleeBeforeCallerGood) {
+  char str[] = R"(
+           OpCapability Shader
+           OpCapability VulkanMemoryModelKHR
+           OpExtension "SPV_KHR_vulkan_memory_model"
+           OpMemoryModel Logical VulkanKHR
+           OpEntryPoint GLCompute %main "main"
+%void    = OpTypeVoid
+%voidfn  = OpTypeFunction %void
+%callee  = OpFunction %void None %voidfn
+%3       = OpLabel
+           OpReturn
+           OpFunctionEnd
+%main    = OpFunction %void None %voidfn
+%1       = OpLabel
+%2       = OpFunctionCall %void %callee
+           OpReturn
+           OpFunctionEnd
+)";
+
+  CompileSuccessfully(str, SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
 // TODO(umar): Test optional instructions
 
 }  // namespace