[spirv-reader][ir] Emit OpControlBarrier
Add support for emitting the correct barrier types based on a control
barrier.
Bug: 401001910
Change-Id: Ic076f2374b6ea0cf0df6f4e3d774ca8258582498
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/232174
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.bazel b/src/tint/lang/spirv/reader/parser/BUILD.bazel
index 699fe19..bb3d2a2 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.bazel
+++ b/src/tint/lang/spirv/reader/parser/BUILD.bazel
@@ -81,6 +81,7 @@
alwayslink = True,
srcs = [
"atomics_test.cc",
+ "barrier_test.cc",
"binary_test.cc",
"bit_test.cc",
"branch_test.cc",
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.cmake b/src/tint/lang/spirv/reader/parser/BUILD.cmake
index da6f3f8..02e0870 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.cmake
+++ b/src/tint/lang/spirv/reader/parser/BUILD.cmake
@@ -90,6 +90,7 @@
################################################################################
tint_add_target(tint_lang_spirv_reader_parser_test test
lang/spirv/reader/parser/atomics_test.cc
+ lang/spirv/reader/parser/barrier_test.cc
lang/spirv/reader/parser/binary_test.cc
lang/spirv/reader/parser/bit_test.cc
lang/spirv/reader/parser/branch_test.cc
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.gn b/src/tint/lang/spirv/reader/parser/BUILD.gn
index 3e05963..a806fd4 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.gn
+++ b/src/tint/lang/spirv/reader/parser/BUILD.gn
@@ -89,6 +89,7 @@
tint_unittests_source_set("unittests") {
sources = [
"atomics_test.cc",
+ "barrier_test.cc",
"binary_test.cc",
"bit_test.cc",
"branch_test.cc",
diff --git a/src/tint/lang/spirv/reader/parser/barrier_test.cc b/src/tint/lang/spirv/reader/parser/barrier_test.cc
new file mode 100644
index 0000000..b6615e9
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/barrier_test.cc
@@ -0,0 +1,279 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/reader/parser/helper_test.h"
+
+namespace tint::spirv::reader {
+namespace {
+
+TEST_F(SpirvParserTest, ControlBarrier_WorkgroupBarrier) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %main "main"
+ OpName %helper "helper"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_264 = OpConstant %uint 264
+ %helper = OpFunction %void None %1
+ %4 = OpLabel
+ OpControlBarrier %uint_2 %uint_2 %uint_264
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )",
+ R"(
+%helper = func():void {
+ $B1: {
+ %2:void = workgroupBarrier
+ ret
+ }
+}
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, ControlBarrier_StorageBarrier) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %main "main"
+ OpName %helper "helper"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_1 = OpConstant %uint 1
+ %uint_72 = OpConstant %uint 72
+ %helper = OpFunction %void None %1
+ %4 = OpLabel
+ OpControlBarrier %uint_2 %uint_2 %uint_72
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )",
+ R"(
+%helper = func():void {
+ $B1: {
+ %2:void = storageBarrier
+ ret
+ }
+}
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, ControlBarrier_TextureBarrier) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %main "main"
+ OpName %helper "helper"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_1 = OpConstant %uint 1
+ %uint_2056 = OpConstant %uint 2056
+ %helper = OpFunction %void None %1
+ %4 = OpLabel
+ OpControlBarrier %uint_2 %uint_2 %uint_2056
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )",
+ R"(
+%helper = func():void {
+ $B1: {
+ %2:void = textureBarrier
+ ret
+ }
+}
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, ControlBarrier_WorkgroupAndTextureAndStorageBarrier) {
+ // Check that we emit multiple adjacent barrier calls when the flags
+ // are combined.
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %main "main"
+ OpName %helper "helper"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_x948 = OpConstant %uint 0x948
+ %helper = OpFunction %void None %1
+ %4 = OpLabel
+ OpControlBarrier %uint_2 %uint_2 %uint_x948
+ OpReturn
+ OpFunctionEnd
+ %main = OpFunction %void None %1
+ %5 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )",
+ R"(
+%helper = func():void {
+ $B1: {
+ %2:void = workgroupBarrier
+ %3:void = storageBarrier
+ %4:void = textureBarrier
+ ret
+ }
+}
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+ $B2: {
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserDeathTest, ControlBarrier_ErrBarrierInvalidExecution) {
+ auto* src = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_3 = OpConstant %uint 3
+ %uint_2 = OpConstant %uint 2
+ %uint_264 = OpConstant %uint 264
+ %main = OpFunction %void None %1
+ %4 = OpLabel
+ OpControlBarrier %uint_3 %uint_2 %uint_264
+ OpReturn
+ OpFunctionEnd
+ )";
+ EXPECT_DEATH_IF_SUPPORTED({ auto _ = Run(src); }, "internal compiler error");
+}
+
+TEST_F(SpirvParserDeathTest, ControlBarrier_ErrBarrierSemanticsMissingAcquireRelease) {
+ auto* src = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_2 = OpConstant %uint 2
+ %uint_0 = OpConstant %uint 0
+ %main = OpFunction %void None %1
+ %4 = OpLabel
+ OpControlBarrier %uint_2 %uint_2 %uint_0
+ OpReturn
+ OpFunctionEnd
+ )";
+ EXPECT_DEATH_IF_SUPPORTED({ auto _ = Run(src); }, "internal compiler error");
+}
+
+TEST_F(SpirvParserDeathTest, ControlBarrier_ErrStorageBarrierInvalidMemory) {
+ auto* src = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %uint_2 = OpConstant %uint 2
+ %uint_72 = OpConstant %uint 72
+ %main = OpFunction %void None %1
+ %4 = OpLabel
+ OpControlBarrier %uint_2 %uint_1 %uint_72
+ OpReturn
+ OpFunctionEnd
+ )";
+ EXPECT_DEATH_IF_SUPPORTED({ auto _ = Run(src); }, "internal compiler error");
+}
+
+TEST_F(SpirvParserDeathTest, ControlBarrier_ErrTextureBarrierInvalidMemory) {
+ auto* src = R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint GLCompute %main "main"
+ OpExecutionMode %main LocalSize 1 1 1
+ OpName %main "main"
+ %void = OpTypeVoid
+ %1 = OpTypeFunction %void
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+ %uint_2 = OpConstant %uint 2
+ %uint_2056 = OpConstant %uint 2056
+ %main = OpFunction %void None %1
+ %4 = OpLabel
+ OpControlBarrier %uint_2 %uint_1 %uint_2056
+ OpReturn
+ OpFunctionEnd
+ )";
+ EXPECT_DEATH_IF_SUPPORTED({ auto _ = Run(src); }, "internal compiler error");
+}
+
+} // namespace
+} // namespace tint::spirv::reader
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index cbd9a5b..0fd6b74 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -1190,6 +1190,9 @@
case spv::Op::OpAtomicIDecrement:
EmitSpirvBuiltinCall(inst, spirv::BuiltinFn::kAtomicIDecrement);
break;
+ case spv::Op::OpControlBarrier:
+ EmitControlBarrier(inst);
+ break;
default:
TINT_UNIMPLEMENTED()
<< "unhandled SPIR-V instruction: " << static_cast<uint32_t>(inst.opcode());
@@ -1215,6 +1218,53 @@
}
}
+ void EmitControlBarrier(const spvtools::opt::Instruction& inst) {
+ auto get_constant = [&](uint32_t idx) {
+ uint32_t id = inst.GetSingleWordOperand(idx);
+ if (auto* constant = spirv_context_->get_constant_mgr()->FindDeclaredConstant(id)) {
+ return constant->GetU32();
+ }
+ TINT_ICE() << "invalid or missing operands for control barrier";
+ };
+
+ uint32_t execution = get_constant(0);
+ uint32_t memory = get_constant(1);
+ uint32_t semantics = get_constant(2);
+
+ if (execution != uint32_t(spv::Scope::Workgroup)) {
+ TINT_ICE() << "unsupported control barrier execution scope: "
+ << "expected Workgroup (2), got: " << execution;
+ }
+
+ if (semantics & uint32_t(spv::MemorySemanticsMask::AcquireRelease)) {
+ semantics &= ~static_cast<uint32_t>(spv::MemorySemanticsMask::AcquireRelease);
+ } else {
+ TINT_ICE() << "control barrier semantics requires acquire and release";
+ }
+ if (memory != uint32_t(spv::Scope::Workgroup)) {
+ TINT_ICE() << "control barrier requires workgroup memory scope";
+ }
+
+ if (semantics & uint32_t(spv::MemorySemanticsMask::WorkgroupMemory)) {
+ EmitWithoutSpvResult(b_.Call(ty_.void_(), core::BuiltinFn::kWorkgroupBarrier));
+ semantics &= ~static_cast<uint32_t>(spv::MemorySemanticsMask::WorkgroupMemory);
+ }
+
+ if (semantics & uint32_t(spv::MemorySemanticsMask::UniformMemory)) {
+ EmitWithoutSpvResult(b_.Call(ty_.void_(), core::BuiltinFn::kStorageBarrier));
+ semantics &= ~static_cast<uint32_t>(spv::MemorySemanticsMask::UniformMemory);
+ }
+
+ if (semantics & uint32_t(spv::MemorySemanticsMask::ImageMemory)) {
+ EmitWithoutSpvResult(b_.Call(ty_.void_(), core::BuiltinFn::kTextureBarrier));
+ semantics &= ~static_cast<uint32_t>(spv::MemorySemanticsMask::ImageMemory);
+ }
+
+ if (semantics) {
+ TINT_ICE() << "unsupported control barrier semantics: " << semantics;
+ }
+ }
+
void CheckAtomicNotFloat(const spvtools::opt::Instruction& inst) {
auto* ty = Type(inst.type_id());
if (ty->IsFloatScalar()) {