Add WebGPU Execution scope check (#2148)

Fixes #2069
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index ac31ec0..8f29ae0 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -90,6 +90,17 @@
     }
   }
 
+  // WebGPU Specific rules
+  if (spvIsWebGPUEnv(_.context()->target_env)) {
+    // Scope for execution must be limited to Workgroup or Subgroup
+    if (value != SpvScopeWorkgroup && value != SpvScopeSubgroup) {
+      return _.diag(SPV_ERROR_INVALID_DATA, inst)
+             << spvOpcodeString(opcode)
+             << ": in WebGPU environment Execution Scope is limited to "
+                "Workgroup and Subgroup";
+    }
+  }
+
   // TODO(atgoo@github.com) Add checks for OpenCL and OpenGL environments.
 
   // General SPIRV rules
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index 9652299..63d6c52 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -28,18 +28,17 @@
 
 using ValidateBarriers = spvtest::ValidateBase<bool>;
 
-std::string GenerateShaderCode(
-    const std::string& body,
-    const std::string& capabilities_and_extensions = "",
-    const std::string& execution_model = "GLCompute") {
+std::string GenerateShaderCodeImpl(
+    const std::string& body, const std::string& capabilities_and_extensions,
+    const std::string& definitions, const std::string& execution_model,
+    const std::string& memory_model) {
   std::ostringstream ss;
   ss << R"(
 OpCapability Shader
-OpCapability Int64
 )";
 
   ss << capabilities_and_extensions;
-  ss << "OpMemoryModel Logical GLSL450\n";
+  ss << memory_model << std::endl;
   ss << "OpEntryPoint " << execution_model << " %main \"main\"\n";
   if (execution_model == "Fragment") {
     ss << "OpExecutionMode %main OriginUpperLeft\n";
@@ -56,16 +55,15 @@
 %bool = OpTypeBool
 %f32 = OpTypeFloat 32
 %u32 = OpTypeInt 32 0
-%u64 = OpTypeInt 64 0
 
 %f32_0 = OpConstant %f32 0
 %f32_1 = OpConstant %f32 1
 %u32_0 = OpConstant %u32 0
 %u32_1 = OpConstant %u32 1
 %u32_4 = OpConstant %u32 4
-%u64_0 = OpConstant %u64 0
-%u64_1 = OpConstant %u64 1
-
+)";
+  ss << definitions;
+  ss << R"(
 %cross_device = OpConstant %u32 0
 %device = OpConstant %u32 1
 %workgroup = OpConstant %u32 2
@@ -96,6 +94,42 @@
   return ss.str();
 }
 
+std::string GenerateShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "",
+    const std::string& execution_model = "GLCompute") {
+  const std::string int64_capability = R"(
+OpCapability Int64
+)";
+  const std::string int64_declarations = R"(
+%u64 = OpTypeInt 64 0
+%u64_0 = OpConstant %u64 0
+%u64_1 = OpConstant %u64 1
+)";
+  const std::string memory_model = "OpMemoryModel Logical GLSL450";
+  return GenerateShaderCodeImpl(
+      body, int64_capability + capabilities_and_extensions, int64_declarations,
+      execution_model, memory_model);
+}
+
+std::string GenerateWebGPUShaderCode(
+    const std::string& body,
+    const std::string& capabilities_and_extensions = "",
+    const std::string& execution_model = "GLCompute") {
+  const std::string vulkan_memory_capability = R"(
+OpCapability VulkanMemoryModelKHR
+)";
+  const std::string vulkan_memory_extension = R"(
+OpExtension "SPV_KHR_vulkan_memory_model"
+)";
+  const std::string memory_model = "OpMemoryModel Logical VulkanKHR";
+  return GenerateShaderCodeImpl(body,
+                                vulkan_memory_capability +
+                                    capabilities_and_extensions +
+                                    vulkan_memory_extension,
+                                "", execution_model, memory_model);
+}
+
 std::string GenerateKernelCode(
     const std::string& body,
     const std::string& capabilities_and_extensions = "") {
@@ -212,6 +246,16 @@
   ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
 }
 
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUSuccess) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %device %none
+OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_WEBGPU_0));
+}
+
 TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv12) {
   const std::string body = R"(
 OpControlBarrier %device %device %none
@@ -322,6 +366,18 @@
                         "is limited to Workgroup and Subgroup"));
 }
 
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUExecutionScopeDevice) {
+  const std::string body = R"(
+OpControlBarrier %device %workgroup %none
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("ControlBarrier: in WebGPU environment Execution Scope "
+                        "is limited to Workgroup and Subgroup"));
+}
+
 TEST_F(ValidateBarriers, OpControlBarrierVulkanMemoryScopeSubgroup) {
   const std::string body = R"(
 OpControlBarrier %subgroup %subgroup %none