Update OpControlBarriers rules for WebGPU (#2769)

* Update OpControlBarriers rules for WebGPU

Part of #2724
diff --git a/source/val/validate_memory_semantics.cpp b/source/val/validate_memory_semantics.cpp
index 0088cdd..c8df405 100644
--- a/source/val/validate_memory_semantics.cpp
+++ b/source/val/validate_memory_semantics.cpp
@@ -57,36 +57,59 @@
   }
 
   if (spvIsWebGPUEnv(_.context()->target_env)) {
-    uint32_t valid_bits = SpvMemorySemanticsUniformMemoryMask |
-                          SpvMemorySemanticsWorkgroupMemoryMask |
-                          SpvMemorySemanticsImageMemoryMask |
-                          SpvMemorySemanticsOutputMemoryKHRMask |
-                          SpvMemorySemanticsMakeAvailableKHRMask |
-                          SpvMemorySemanticsMakeVisibleKHRMask;
-    if (!spvOpcodeIsAtomicOp(inst->opcode())) {
-      valid_bits |= SpvMemorySemanticsAcquireReleaseMask;
-    }
-
-    if (value & ~valid_bits) {
-      if (spvOpcodeIsAtomicOp(inst->opcode())) {
+    if (inst->opcode() == SpvOpControlBarrier) {
+      if (!(value & SpvMemorySemanticsAcquireReleaseMask)) {
         return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "WebGPU spec disallows, for OpAtomic*, any bit masks in "
-                  "Memory Semantics that are not UniformMemory, "
-                  "WorkgroupMemory, ImageMemory, or OutputMemoryKHR";
-      } else {
-        return _.diag(SPV_ERROR_INVALID_DATA, inst)
-               << "WebGPU spec disallows any bit masks in Memory Semantics "
-                  "that are not AcquireRelease, UniformMemory, "
-                  "WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
-                  "MakeAvailableKHR, or MakeVisibleKHR";
+               << "For WebGPU, AcquireRelease must be set for Memory Semantics "
+                  "of OpControlBarrier.";
       }
-    }
 
-    if (!spvOpcodeIsAtomicOp(inst->opcode()) &&
-        !(value & SpvMemorySemanticsAcquireReleaseMask)) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << "WebGPU spec requires AcquireRelease to set in Memory "
-                "Semantics.";
+      if (!(value & SpvMemorySemanticsWorkgroupMemoryMask)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "For WebGPU, WorkgroupMemory must be set for Memory "
+                  "Semantics of OpControlBarrier.";
+      }
+
+      uint32_t valid_bits = SpvMemorySemanticsAcquireReleaseMask |
+                            SpvMemorySemanticsWorkgroupMemoryMask;
+      if (value & ~valid_bits) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "For WebGPU only WorkgroupMemory and AcquireRelease may be "
+                  "set for Memory Semantics of OpControlBarrier.";
+      }
+    } else {
+      // TODO(2723): Rewrite this to be in the style of above and simplify.
+      uint32_t valid_bits = SpvMemorySemanticsUniformMemoryMask |
+                            SpvMemorySemanticsWorkgroupMemoryMask |
+                            SpvMemorySemanticsImageMemoryMask |
+                            SpvMemorySemanticsOutputMemoryKHRMask |
+                            SpvMemorySemanticsMakeAvailableKHRMask |
+                            SpvMemorySemanticsMakeVisibleKHRMask;
+      if (!spvOpcodeIsAtomicOp(inst->opcode())) {
+        valid_bits |= SpvMemorySemanticsAcquireReleaseMask;
+      }
+
+      if (value & ~valid_bits) {
+        if (spvOpcodeIsAtomicOp(inst->opcode())) {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "WebGPU spec disallows, for OpAtomic*, any bit masks in "
+                    "Memory Semantics that are not UniformMemory, "
+                    "WorkgroupMemory, ImageMemory, or OutputMemoryKHR";
+        } else {
+          return _.diag(SPV_ERROR_INVALID_DATA, inst)
+                 << "WebGPU spec disallows any bit masks in Memory Semantics "
+                    "that are not AcquireRelease, UniformMemory, "
+                    "WorkgroupMemory, ImageMemory, OutputMemoryKHR, "
+                    "MakeAvailableKHR, or MakeVisibleKHR";
+        }
+      }
+
+      if (!spvOpcodeIsAtomicOp(inst->opcode()) &&
+          !(value & SpvMemorySemanticsAcquireReleaseMask)) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << "WebGPU spec requires AcquireRelease to set in Memory "
+                  "Semantics.";
+      }
     }
   }
 
diff --git a/source/val/validate_scopes.cpp b/source/val/validate_scopes.cpp
index c607984..32a52a8 100644
--- a/source/val/validate_scopes.cpp
+++ b/source/val/validate_scopes.cpp
@@ -122,7 +122,6 @@
 
   // WebGPU Specific rules
   if (spvIsWebGPUEnv(_.context()->target_env)) {
-    // Scope for execution must be limited to Workgroup or Subgroup
     if (value != SpvScopeWorkgroup) {
       return _.diag(SPV_ERROR_INVALID_DATA, inst)
              << spvOpcodeString(opcode)
@@ -229,12 +228,21 @@
 
   // WebGPU specific rules
   if (spvIsWebGPUEnv(_.context()->target_env)) {
-    if (value != SpvScopeWorkgroup && value != SpvScopeInvocation &&
-        value != SpvScopeQueueFamilyKHR) {
-      return _.diag(SPV_ERROR_INVALID_DATA, inst)
-             << spvOpcodeString(opcode)
-             << ": in WebGPU environment Memory Scope is limited to "
-             << "Workgroup, Invocation, and QueueFamilyKHR";
+    if (inst->opcode() == SpvOpControlBarrier) {
+      if (value != SpvScopeWorkgroup) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << spvOpcodeString(opcode)
+               << ": in WebGPU environment Memory Scope is limited to "
+               << "Workgroup for OpControlBarrier";
+      }
+    } else {
+      if (value != SpvScopeWorkgroup && value != SpvScopeInvocation &&
+          value != SpvScopeQueueFamilyKHR) {
+        return _.diag(SPV_ERROR_INVALID_DATA, inst)
+               << spvOpcodeString(opcode)
+               << ": in WebGPU environment Memory Scope is limited to "
+               << "Workgroup, Invocation, and QueueFamilyKHR";
+      }
     }
   }
 
diff --git a/test/val/val_barriers_test.cpp b/test/val/val_barriers_test.cpp
index 2214197..1d71e90 100644
--- a/test/val/val_barriers_test.cpp
+++ b/test/val/val_barriers_test.cpp
@@ -82,8 +82,10 @@
 %release_uniform_workgroup = OpConstant %u32 324
 %acquire_and_release_uniform = OpConstant %u32 70
 %acquire_release_subgroup = OpConstant %u32 136
+%acquire_release_workgroup = OpConstant %u32 264
 %uniform = OpConstant %u32 64
 %uniform_workgroup = OpConstant %u32 320
+%workgroup_memory = OpConstant %u32 256
 
 
 %main = OpFunction %void None %func
@@ -251,7 +253,7 @@
 
 TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireReleaseSuccess) {
   const std::string body = R"(
-OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
+OpControlBarrier %workgroup %workgroup %acquire_release_workgroup
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
@@ -260,25 +262,39 @@
 
 TEST_F(ValidateBarriers, OpControlBarrierWebGPURelaxedFailure) {
   const std::string body = R"(
-OpControlBarrier %workgroup %workgroup %uniform_workgroup
+OpControlBarrier %workgroup %workgroup %workgroup
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(getDiagnosticString(),
-              HasSubstr("WebGPU spec requires AcquireRelease to set"));
+              HasSubstr("For WebGPU, AcquireRelease must be set for Memory "
+                        "Semantics of OpControlBarrier"));
 }
 
-TEST_F(ValidateBarriers, OpControlBarrierWebGPUAcquireFailure) {
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUMissingWorkgroupFailure) {
   const std::string body = R"(
-OpControlBarrier %workgroup %workgroup %acquire_uniform_workgroup
+OpControlBarrier %workgroup %workgroup %acquire_release
+)";
+
+  CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
+  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, WorkgroupMemory must be set for Memory "
+                        "Semantics"));
+}
+
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUUniformFailure) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %workgroup %acquire_release_uniform_workgroup
 )";
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
   EXPECT_THAT(
       getDiagnosticString(),
-      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+      HasSubstr("For WebGPU only WorkgroupMemory and AcquireRelease may be set "
+                "for Memory Semantics of OpControlBarrier."));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierWebGPUReleaseFailure) {
@@ -288,9 +304,9 @@
 
   CompileSuccessfully(GenerateWebGPUShaderCode(body), SPV_ENV_WEBGPU_0);
   EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_WEBGPU_0));
-  EXPECT_THAT(
-      getDiagnosticString(),
-      HasSubstr("WebGPU spec disallows any bit masks in Memory Semantics"));
+  EXPECT_THAT(getDiagnosticString(),
+              HasSubstr("For WebGPU, AcquireRelease must be set for Memory "
+                        "Semantics of OpControlBarrier"));
 }
 
 TEST_F(ValidateBarriers, OpControlBarrierExecutionModelFragmentSpirv12) {
@@ -461,6 +477,18 @@
                         "cannot be CrossDevice"));
 }
 
+TEST_F(ValidateBarriers, OpControlBarrierWebGPUMemoryScopeNonWorkgroup) {
+  const std::string body = R"(
+OpControlBarrier %workgroup %subgroup %acquire_release_workgroup
+)";
+
+  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 Memory Scope is "
+                        "limited to Workgroup for OpControlBarrier"));
+}
+
 TEST_F(ValidateBarriers, OpControlBarrierAcquireAndRelease) {
   const std::string body = R"(
 OpControlBarrier %device %device %acquire_and_release_uniform