[hlsl] Add decomposing for storage atomics
This CL adds decomposition of storage atomics to the HLSL transform.
Bug: 349867642
Change-Id: If5ef74c705655f0b1adc0a6072b83923c90a9bdb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/198454
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/intrinsic/table.cc b/src/tint/lang/core/intrinsic/table.cc
index 80bc5b0..cc6e776 100644
--- a/src/tint/lang/core/intrinsic/table.cc
+++ b/src/tint/lang/core/intrinsic/table.cc
@@ -213,7 +213,7 @@
auto candidate = ScoreOverload<ScoreMode::kEarlyReject>(context, overload, template_args,
args, earliest_eval_stage);
if (candidate.score == 0) {
- match_idx = overload_idx;
+ match_idx = candidates.Length();
num_matched++;
}
candidates.Push(std::move(candidate));
diff --git a/src/tint/lang/hlsl/writer/builtin_test.cc b/src/tint/lang/hlsl/writer/builtin_test.cc
index 5842b37..b59ba17 100644
--- a/src/tint/lang/hlsl/writer/builtin_test.cc
+++ b/src/tint/lang/hlsl/writer/builtin_test.cc
@@ -162,7 +162,7 @@
)");
}
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicStore) {
+TEST_F(HlslWriterTest, BuiltinStorageAtomicStore) {
auto* sb = ty.Struct(mod.symbols.New("SB"), {
{mod.symbols.New("padding"), ty.vec4<f32>()},
{mod.symbols.New("a"), ty.atomic<i32>()},
@@ -182,10 +182,38 @@
ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.InterlockedExchange(int(16u), 123, v_1);
+}
+
)");
}
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicLoad) {
+TEST_F(HlslWriterTest, BuiltinStorageAtomicStoreDirect) {
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Call(ty.void_(), core::BuiltinFn::kAtomicStore, var, 123_i);
+ b.Return(func);
+ });
+
+ ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+ EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.InterlockedExchange(int(0u), 123, v_1);
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, BuiltinStorageAtomicLoad) {
auto* sb = ty.Struct(mod.symbols.New("SB"), {
{mod.symbols.New("padding"), ty.vec4<f32>()},
{mod.symbols.New("a"), ty.atomic<i32>()},
@@ -205,33 +233,40 @@
ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.InterlockedOr(int(16u), 0, v_1);
+ int x = v_1;
+}
+
)");
}
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicAdd) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
+TEST_F(HlslWriterTest, BuiltinStorageAtomicLoadDirect) {
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
var->SetBindingPoint(0, 0);
b.ir.root_block->Append(var);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicAdd,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
+ b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicLoad, var));
b.Return(func);
});
ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.InterlockedOr(int(0u), 0, v_1);
+ int x = v_1;
+}
+
)");
}
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicSub) {
+TEST_F(HlslWriterTest, BuiltinStorageAtomicSub) {
auto* sb = ty.Struct(mod.symbols.New("SB"), {
{mod.symbols.New("padding"), ty.vec4<f32>()},
{mod.symbols.New("a"), ty.atomic<i32>()},
@@ -251,33 +286,40 @@
ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.InterlockedAdd(int(16u), -(123), v_1);
+ int x = v_1;
+}
+
)");
}
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicMax) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
+TEST_F(HlslWriterTest, BuiltinStorageAtomicSubDirect) {
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
var->SetBindingPoint(0, 0);
b.ir.root_block->Append(var);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicMax,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
+ b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicSub, var, 123_i));
b.Return(func);
});
ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.InterlockedAdd(int(0u), -(123), v_1);
+ int x = v_1;
+}
+
)");
}
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicMin) {
+TEST_F(HlslWriterTest, BuiltinStorageAtomicCompareExchangeWeak) {
auto* sb = ty.Struct(mod.symbols.New("SB"), {
{mod.symbols.New("padding"), ty.vec4<f32>()},
{mod.symbols.New("a"), ty.atomic<i32>()},
@@ -290,133 +332,58 @@
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicMin,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
- b.Return(func);
- });
-
- ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
- EXPECT_EQ(output_.hlsl, R"(
-)");
-}
-
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicAnd) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicAnd,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
- b.Return(func);
- });
-
- ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
- EXPECT_EQ(output_.hlsl, R"(
-)");
-}
-
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicOr) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicOr,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
- b.Return(func);
- });
-
- ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
- EXPECT_EQ(output_.hlsl, R"(
-)");
-}
-
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicXor) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicXor,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 2_u), 123_i));
- b.Return(func);
- });
-
- ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
- EXPECT_EQ(output_.hlsl, R"(
-)");
-}
-
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicExchange) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicExchange,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 2_u), 123_i));
- b.Return(func);
- });
-
- ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
- EXPECT_EQ(output_.hlsl, R"(
-)");
-}
-
-TEST_F(HlslWriterTest, DISABLED_BuiltinStorageAtomicCompareExchangeWeak) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* out = ty.Struct(
- mod.symbols.New("__atomic_compare_exchange_result"),
- {{mod.symbols.New("old_value"), ty.i32()}, {mod.symbols.New("exchanged"), ty.bool_()}});
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
b.Let("x",
- b.Call(out, core::BuiltinFn::kAtomicCompareExchangeWeak,
+ b.Call(core::type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32()),
+ core::BuiltinFn::kAtomicCompareExchangeWeak,
b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i, 345_i));
b.Return(func);
});
ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
- EXPECT_EQ(output_.hlsl, R"(
+ EXPECT_EQ(output_.hlsl, R"(struct atomic_compare_exchange_result_i32 {
+ int old_value;
+ bool exchanged;
+};
+
+
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.InterlockedCompareExchange(int(16u), 123, 345, v_1);
+ int v_2 = v_1;
+ atomic_compare_exchange_result_i32 x = {v_2, (v_2 == 123)};
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, BuiltinStorageAtomicCompareExchangeWeakDirect) {
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("x", b.Call(core::type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32()),
+ core::BuiltinFn::kAtomicCompareExchangeWeak, var, 123_i, 345_i));
+ b.Return(func);
+ });
+
+ ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+ EXPECT_EQ(output_.hlsl, R"(struct atomic_compare_exchange_result_i32 {
+ int old_value;
+ bool exchanged;
+};
+
+
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.InterlockedCompareExchange(int(0u), 123, 345, v_1);
+ int v_2 = v_1;
+ atomic_compare_exchange_result_i32 x = {v_2, (v_2 == 123)};
+}
+
)");
}
@@ -428,48 +395,67 @@
out << data.interlock;
return out;
}
-using HlslBuiltinWorkgroupAtomic = HlslWriterTestWithParam<AtomicData>;
-TEST_P(HlslBuiltinWorkgroupAtomic, Access) {
+
+using HlslBuiltinAtomic = HlslWriterTestWithParam<AtomicData>;
+TEST_P(HlslBuiltinAtomic, IndirectAccess) {
auto param = GetParam();
- auto* var = b.Var("v", workgroup, ty.atomic<i32>(), core::Access::kReadWrite);
+ auto* sb = ty.Struct(mod.symbols.New("SB"), {
+ {mod.symbols.New("padding"), ty.vec4<f32>()},
+ {mod.symbols.New("a"), ty.atomic<i32>()},
+ {mod.symbols.New("b"), ty.atomic<u32>()},
+ });
+
+ auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
b.ir.root_block->Append(var);
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kCompute);
- func->SetWorkgroupSize(1, 1, 1);
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("x", b.Call(ty.i32(), param.fn,
+ b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
+ b.Return(func);
+ });
+
+ ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+ EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.)" + std::string(param.interlock) +
+ R"((int(16u), 123, v_1);
+ int x = v_1;
+}
+
+)");
+}
+
+TEST_P(HlslBuiltinAtomic, DirectAccess) {
+ auto param = GetParam();
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
b.Let("x", b.Call(ty.i32(), param.fn, var, 123_i));
b.Return(func);
});
ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
- EXPECT_EQ(output_.hlsl, R"(struct foo_inputs {
- uint tint_local_index : SV_GroupIndex;
-};
-
-
-groupshared int v;
-void foo_inner(uint tint_local_index) {
- if ((tint_local_index == 0u)) {
- int v_1 = 0;
- InterlockedExchange(v, 0, v_1);
- }
- GroupMemoryBarrierWithGroupSync();
- int v_2 = 0;
- )" + std::string(param.interlock) +
- R"((v, 123, v_2);
- int x = v_2;
-}
-
-[numthreads(1, 1, 1)]
-void foo(foo_inputs inputs) {
- foo_inner(inputs.tint_local_index);
+ EXPECT_EQ(output_.hlsl, R"(
+RWByteAddressBuffer v : register(u0);
+void foo() {
+ int v_1 = 0;
+ v.)" + std::string(param.interlock) +
+ R"((int(0u), 123, v_1);
+ int x = v_1;
}
)");
}
INSTANTIATE_TEST_SUITE_P(HlslWriterTest,
- HlslBuiltinWorkgroupAtomic,
+ HlslBuiltinAtomic,
testing::Values(AtomicData{core::BuiltinFn::kAtomicAdd, "InterlockedAdd"},
AtomicData{core::BuiltinFn::kAtomicMax, "InterlockedMax"},
AtomicData{core::BuiltinFn::kAtomicMin, "InterlockedMin"},
@@ -698,6 +684,58 @@
)");
}
+using HlslBuiltinWorkgroupAtomic = HlslWriterTestWithParam<AtomicData>;
+TEST_P(HlslBuiltinWorkgroupAtomic, Access) {
+ auto param = GetParam();
+ auto* var = b.Var("v", workgroup, ty.atomic<i32>(), core::Access::kReadWrite);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kCompute);
+ func->SetWorkgroupSize(1, 1, 1);
+
+ b.Append(func->Block(), [&] {
+ b.Let("x", b.Call(ty.i32(), param.fn, var, 123_i));
+ b.Return(func);
+ });
+
+ ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+ EXPECT_EQ(output_.hlsl, R"(struct foo_inputs {
+ uint tint_local_index : SV_GroupIndex;
+};
+
+
+groupshared int v;
+void foo_inner(uint tint_local_index) {
+ if ((tint_local_index == 0u)) {
+ int v_1 = 0;
+ InterlockedExchange(v, 0, v_1);
+ }
+ GroupMemoryBarrierWithGroupSync();
+ int v_2 = 0;
+ )" + std::string(param.interlock) +
+ R"((v, 123, v_2);
+ int x = v_2;
+}
+
+[numthreads(1, 1, 1)]
+void foo(foo_inputs inputs) {
+ foo_inner(inputs.tint_local_index);
+}
+
+)");
+}
+
+INSTANTIATE_TEST_SUITE_P(HlslWriterTest,
+ HlslBuiltinWorkgroupAtomic,
+ testing::Values(AtomicData{core::BuiltinFn::kAtomicAdd, "InterlockedAdd"},
+ AtomicData{core::BuiltinFn::kAtomicMax, "InterlockedMax"},
+ AtomicData{core::BuiltinFn::kAtomicMin, "InterlockedMin"},
+ AtomicData{core::BuiltinFn::kAtomicAnd, "InterlockedAnd"},
+ AtomicData{core::BuiltinFn::kAtomicOr, "InterlockedOr"},
+ AtomicData{core::BuiltinFn::kAtomicXor, "InterlockedXor"},
+ AtomicData{core::BuiltinFn::kAtomicExchange,
+ "InterlockedExchange"}));
+
TEST_F(HlslWriterTest, BuiltinSignScalar) {
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_storage_access.cc b/src/tint/lang/hlsl/writer/raise/decompose_storage_access.cc
index 07a2389..c04e4f8 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_storage_access.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_storage_access.cc
@@ -97,8 +97,24 @@
[&](core::ir::Access* a) { usage_worklist.Push(a); },
[&](core::ir::Let* l) { usage_worklist.Push(l); },
[&](core::ir::CoreBuiltinCall* call) {
- TINT_ASSERT(call->Func() == core::BuiltinFn::kArrayLength);
- usage_worklist.Push(call);
+ switch (call->Func()) {
+ case core::BuiltinFn::kArrayLength:
+ case core::BuiltinFn::kAtomicAnd:
+ case core::BuiltinFn::kAtomicOr:
+ case core::BuiltinFn::kAtomicXor:
+ case core::BuiltinFn::kAtomicMin:
+ case core::BuiltinFn::kAtomicMax:
+ case core::BuiltinFn::kAtomicAdd:
+ case core::BuiltinFn::kAtomicSub:
+ case core::BuiltinFn::kAtomicExchange:
+ case core::BuiltinFn::kAtomicCompareExchangeWeak:
+ case core::BuiltinFn::kAtomicStore:
+ case core::BuiltinFn::kAtomicLoad:
+ usage_worklist.Push(call);
+ break;
+ default:
+ TINT_UNREACHABLE() << call->Func();
+ }
},
//
TINT_ICE_ON_NO_MATCH);
@@ -139,7 +155,46 @@
let->Destroy();
},
[&](core::ir::CoreBuiltinCall* call) {
- ArrayLength(var, call, var_ty->StoreType(), 0);
+ switch (call->Func()) {
+ case core::BuiltinFn::kArrayLength:
+ ArrayLength(var, call, var_ty->StoreType(), 0);
+ break;
+ case core::BuiltinFn::kAtomicAnd:
+ AtomicAnd(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicOr:
+ AtomicOr(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicXor:
+ AtomicXor(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicMin:
+ AtomicMin(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicMax:
+ AtomicMax(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicAdd:
+ AtomicAdd(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicSub:
+ AtomicSub(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicExchange:
+ AtomicExchange(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicCompareExchangeWeak:
+ AtomicCompareExchangeWeak(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicStore:
+ AtomicStore(var, call, 0);
+ break;
+ case core::BuiltinFn::kAtomicLoad:
+ AtomicLoad(var, call, 0);
+ break;
+ default:
+ TINT_UNREACHABLE();
+ }
},
TINT_ICE_ON_NO_MATCH);
}
@@ -178,6 +233,120 @@
call->Destroy();
}
+ void Interlocked(core::ir::Var* var,
+ core::ir::CoreBuiltinCall* call,
+ uint32_t offset,
+ BuiltinFn fn) {
+ auto args = call->Args();
+ auto* type = args[1]->Type();
+
+ b.InsertBefore(call, [&] {
+ auto* original_value = b.Var(ty.ptr(function, type));
+ original_value->SetInitializer(b.Zero(type));
+
+ b.MemberCall<hlsl::ir::MemberBuiltinCall>(
+ ty.void_(), fn, var, b.Convert(type, u32(offset)), args[1], original_value);
+ b.LoadWithResult(call->DetachResult(), original_value);
+ });
+ call->Destroy();
+ }
+
+ void AtomicAnd(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ Interlocked(var, call, offset, BuiltinFn::kInterlockedAnd);
+ }
+
+ void AtomicOr(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ Interlocked(var, call, offset, BuiltinFn::kInterlockedOr);
+ }
+
+ void AtomicXor(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ Interlocked(var, call, offset, BuiltinFn::kInterlockedXor);
+ }
+
+ void AtomicMin(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ Interlocked(var, call, offset, BuiltinFn::kInterlockedMin);
+ }
+
+ void AtomicMax(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ Interlocked(var, call, offset, BuiltinFn::kInterlockedMax);
+ }
+
+ void AtomicAdd(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ Interlocked(var, call, offset, BuiltinFn::kInterlockedAdd);
+ }
+
+ void AtomicExchange(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ Interlocked(var, call, offset, BuiltinFn::kInterlockedExchange);
+ }
+
+ // An atomic sub is a negated atomic add
+ void AtomicSub(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ auto args = call->Args();
+ auto* type = args[1]->Type();
+
+ b.InsertBefore(call, [&] {
+ auto* original_value = b.Var(ty.ptr(function, type));
+ original_value->SetInitializer(b.Zero(type));
+
+ auto* val = b.Negation(type, args[1]);
+ b.MemberCall<hlsl::ir::MemberBuiltinCall>(ty.void_(), BuiltinFn::kInterlockedAdd, var,
+ b.Convert(type, u32(offset)), val,
+ original_value);
+ b.LoadWithResult(call->DetachResult(), original_value);
+ });
+ call->Destroy();
+ }
+
+ void AtomicCompareExchangeWeak(core::ir::Var* var,
+ core::ir::CoreBuiltinCall* call,
+ uint32_t offset) {
+ auto args = call->Args();
+ auto* type = args[1]->Type();
+ b.InsertBefore(call, [&] {
+ auto* original_value = b.Var(ty.ptr(function, type));
+ original_value->SetInitializer(b.Zero(type));
+
+ auto* cmp = args[1];
+ b.MemberCall<hlsl::ir::MemberBuiltinCall>(
+ ty.void_(), BuiltinFn::kInterlockedCompareExchange, var,
+ b.Convert(type, u32(offset)), cmp, args[2], original_value);
+
+ auto* o = b.Load(original_value);
+ b.ConstructWithResult(call->DetachResult(), o, b.Equal(ty.bool_(), o, cmp));
+ });
+ call->Destroy();
+ }
+
+ // An atomic load is an Or with 0
+ void AtomicLoad(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ auto* type = call->Result(0)->Type();
+ b.InsertBefore(call, [&] {
+ auto* original_value = b.Var(ty.ptr(function, type));
+ original_value->SetInitializer(b.Zero(type));
+
+ b.MemberCall<hlsl::ir::MemberBuiltinCall>(ty.void_(), BuiltinFn::kInterlockedOr, var,
+ b.Convert(type, u32(offset)), b.Zero(type),
+ original_value);
+ b.LoadWithResult(call->DetachResult(), original_value);
+ });
+ call->Destroy();
+ }
+
+ void AtomicStore(core::ir::Var* var, core::ir::CoreBuiltinCall* call, uint32_t offset) {
+ auto args = call->Args();
+ auto* type = args[1]->Type();
+
+ b.InsertBefore(call, [&] {
+ auto* original_value = b.Var(ty.ptr(function, type));
+ original_value->SetInitializer(b.Zero(type));
+
+ b.MemberCall<hlsl::ir::MemberBuiltinCall>(ty.void_(), BuiltinFn::kInterlockedExchange,
+ var, b.Convert(type, u32(offset)), args[1],
+ original_value);
+ });
+ call->Destroy();
+ }
+
struct OffsetData {
uint32_t byte_offset = 0;
Vector<core::ir::Value*, 4> expr{};
@@ -615,14 +784,50 @@
},
[&](core::ir::Store* store) { Store(store, var, store->From(), *offset); },
[&](core::ir::CoreBuiltinCall* call) {
- // Array length calls require the access
- TINT_ASSERT(call->Func() == core::BuiltinFn::kArrayLength);
-
- // If this access chain is being used in an `arrayLength` call then the
- // access chain _must_ have resolved to the runtime array member of the
- // structure. So, we _must_ have set `obj` to the array member which is a
- // runtime array.
- ArrayLength(var, call, obj, offset->byte_offset);
+ switch (call->Func()) {
+ case core::BuiltinFn::kArrayLength:
+ // If this access chain is being used in an `arrayLength` call then the
+ // access chain _must_ have resolved to the runtime array member of the
+ // structure. So, we _must_ have set `obj` to the array member which is
+ // a runtime array.
+ ArrayLength(var, call, obj, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicAnd:
+ AtomicAnd(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicOr:
+ AtomicOr(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicXor:
+ AtomicXor(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicMin:
+ AtomicMin(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicMax:
+ AtomicMax(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicAdd:
+ AtomicAdd(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicSub:
+ AtomicSub(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicExchange:
+ AtomicExchange(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicCompareExchangeWeak:
+ AtomicCompareExchangeWeak(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicStore:
+ AtomicStore(var, call, offset->byte_offset);
+ break;
+ case core::BuiltinFn::kAtomicLoad:
+ AtomicLoad(var, call, offset->byte_offset);
+ break;
+ default:
+ TINT_UNREACHABLE() << call->Func();
+ }
}, //
TINT_ICE_ON_NO_MATCH);
}
diff --git a/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc b/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
index 786af28..b2e3323 100644
--- a/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
+++ b/src/tint/lang/hlsl/writer/raise/decompose_storage_access_test.cc
@@ -29,10 +29,13 @@
#include <gtest/gtest.h>
+#include <string>
+
#include "src/tint/lang/core/fluent_types.h"
#include "src/tint/lang/core/ir/function.h"
#include "src/tint/lang/core/ir/transform/helper_test.h"
#include "src/tint/lang/core/number.h"
+#include "src/tint/lang/core/type/builtin_structs.h"
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
@@ -1306,7 +1309,7 @@
EXPECT_EQ(expect, str());
}
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicStore) {
+TEST_F(HlslWriterDecomposeStorageAccessTest, StorageAtomicStore) {
auto* sb = ty.Struct(mod.symbols.New("SB"), {
{mod.symbols.New("padding"), ty.vec4<f32>()},
{mod.symbols.New("a"), ty.atomic<i32>()},
@@ -1346,13 +1349,75 @@
EXPECT_EQ(src, str());
auto* expect = R"(
+SB = struct @align(16) {
+ padding:vec4<f32> @offset(0)
+ a:atomic<i32> @offset(16)
+ b:atomic<u32> @offset(20)
+}
+
+$B1: { # root
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = convert 16u
+ %5:void = %v.InterlockedExchange %4, 123i, %3
+ ret
+ }
+}
)";
Run(DecomposeStorageAccess);
EXPECT_EQ(expect, str());
}
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicLoad) {
+TEST_F(HlslWriterDecomposeStorageAccessTest, StorageAtomicStoreDirect) {
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Call(ty.void_(), core::BuiltinFn::kAtomicStore, var, 123_i);
+ b.Return(func);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %v:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:void = atomicStore %v, 123i
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+$B1: { # root
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = convert 0u
+ %5:void = %v.InterlockedExchange %4, 123i, %3
+ ret
+ }
+}
+)";
+ Run(DecomposeStorageAccess);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterDecomposeStorageAccessTest, StorageAtomicLoad) {
auto* sb = ty.Struct(mod.symbols.New("SB"), {
{mod.symbols.New("padding"), ty.vec4<f32>()},
{mod.symbols.New("a"), ty.atomic<i32>()},
@@ -1393,31 +1458,6 @@
EXPECT_EQ(src, str());
auto* expect = R"(
-)";
- Run(DecomposeStorageAccess);
-
- EXPECT_EQ(expect, str());
-}
-
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicAdd) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicAdd,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
- b.Return(func);
- });
-
- auto* src = R"(
SB = struct @align(16) {
padding:vec4<f32> @offset(0)
a:atomic<i32> @offset(16)
@@ -1425,14 +1465,45 @@
}
$B1: { # root
- %v:ptr<storage, SB, read_write> = var @binding_point(0, 0)
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
}
%foo = @fragment func():void {
$B2: {
- %3:ptr<storage, atomic<i32>, read_write> = access %v, 1u
- %4:i32 = atomicAdd %3, 123i
- %x:i32 = let %4
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = convert 16u
+ %5:void = %v.InterlockedOr %4, 0i, %3
+ %6:i32 = load %3
+ %x:i32 = let %6
+ ret
+ }
+}
+)";
+ Run(DecomposeStorageAccess);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(HlslWriterDecomposeStorageAccessTest, StorageAtomicLoadDirect) {
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicLoad, var));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+$B1: { # root
+ %v:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:i32 = atomicLoad %v
+ %x:i32 = let %3
ret
}
}
@@ -1440,13 +1511,27 @@
EXPECT_EQ(src, str());
auto* expect = R"(
+$B1: { # root
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = convert 0u
+ %5:void = %v.InterlockedOr %4, 0i, %3
+ %6:i32 = load %3
+ %x:i32 = let %6
+ ret
+ }
+}
)";
Run(DecomposeStorageAccess);
EXPECT_EQ(expect, str());
}
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicSub) {
+TEST_F(HlslWriterDecomposeStorageAccessTest, StorageAtomicSub) {
auto* sb = ty.Struct(mod.symbols.New("SB"), {
{mod.symbols.New("padding"), ty.vec4<f32>()},
{mod.symbols.New("a"), ty.atomic<i32>()},
@@ -1487,31 +1572,6 @@
EXPECT_EQ(src, str());
auto* expect = R"(
-)";
- Run(DecomposeStorageAccess);
-
- EXPECT_EQ(expect, str());
-}
-
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicMax) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicMax,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
- b.Return(func);
- });
-
- auto* src = R"(
SB = struct @align(16) {
padding:vec4<f32> @offset(0)
a:atomic<i32> @offset(16)
@@ -1519,61 +1579,46 @@
}
$B1: { # root
- %v:ptr<storage, SB, read_write> = var @binding_point(0, 0)
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
}
%foo = @fragment func():void {
$B2: {
- %3:ptr<storage, atomic<i32>, read_write> = access %v, 1u
- %4:i32 = atomicMax %3, 123i
- %x:i32 = let %4
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = negation 123i
+ %5:i32 = convert 16u
+ %6:void = %v.InterlockedAdd %5, %4, %3
+ %7:i32 = load %3
+ %x:i32 = let %7
ret
}
}
)";
- EXPECT_EQ(src, str());
-
- auto* expect = R"(
-)";
Run(DecomposeStorageAccess);
EXPECT_EQ(expect, str());
}
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicMin) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
+TEST_F(HlslWriterDecomposeStorageAccessTest, StorageAtomicSubDirect) {
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
var->SetBindingPoint(0, 0);
b.ir.root_block->Append(var);
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicMin,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
+ b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicSub, var, 123_i));
b.Return(func);
});
auto* src = R"(
-SB = struct @align(16) {
- padding:vec4<f32> @offset(0)
- a:atomic<i32> @offset(16)
- b:atomic<u32> @offset(20)
-}
-
$B1: { # root
- %v:ptr<storage, SB, read_write> = var @binding_point(0, 0)
+ %v:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0)
}
%foo = @fragment func():void {
$B2: {
- %3:ptr<storage, atomic<i32>, read_write> = access %v, 1u
- %4:i32 = atomicMin %3, 123i
- %x:i32 = let %4
+ %3:i32 = atomicSub %v, 123i
+ %x:i32 = let %3
ret
}
}
@@ -1581,60 +1626,28 @@
EXPECT_EQ(src, str());
auto* expect = R"(
-)";
- Run(DecomposeStorageAccess);
-
- EXPECT_EQ(expect, str());
-}
-
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicAnd) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicAnd,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
- b.Return(func);
- });
-
- auto* src = R"(
-SB = struct @align(16) {
- padding:vec4<f32> @offset(0)
- a:atomic<i32> @offset(16)
- b:atomic<u32> @offset(20)
-}
-
$B1: { # root
- %v:ptr<storage, SB, read_write> = var @binding_point(0, 0)
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
}
%foo = @fragment func():void {
$B2: {
- %3:ptr<storage, atomic<i32>, read_write> = access %v, 1u
- %4:i32 = atomicAnd %3, 123i
- %x:i32 = let %4
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = negation 123i
+ %5:i32 = convert 0u
+ %6:void = %v.InterlockedAdd %5, %4, %3
+ %7:i32 = load %3
+ %x:i32 = let %7
ret
}
}
)";
- EXPECT_EQ(src, str());
-
- auto* expect = R"(
-)";
Run(DecomposeStorageAccess);
EXPECT_EQ(expect, str());
}
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicOr) {
+TEST_F(HlslWriterDecomposeStorageAccessTest, StorageAtomicCompareExchangeWeak) {
auto* sb = ty.Struct(mod.symbols.New("SB"), {
{mod.symbols.New("padding"), ty.vec4<f32>()},
{mod.symbols.New("a"), ty.atomic<i32>()},
@@ -1647,153 +1660,9 @@
auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicOr,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
- b.Return(func);
- });
-
- auto* src = R"(
-SB = struct @align(16) {
- padding:vec4<f32> @offset(0)
- a:atomic<i32> @offset(16)
- b:atomic<u32> @offset(20)
-}
-
-$B1: { # root
- %v:ptr<storage, SB, read_write> = var @binding_point(0, 0)
-}
-
-%foo = @fragment func():void {
- $B2: {
- %3:ptr<storage, atomic<i32>, read_write> = access %v, 1u
- %4:i32 = atomicOr %3, 123i
- %x:i32 = let %4
- ret
- }
-}
-)";
- EXPECT_EQ(src, str());
-
- auto* expect = R"(
-)";
- Run(DecomposeStorageAccess);
-
- EXPECT_EQ(expect, str());
-}
-
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicXor) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicXor,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 2_u), 123_i));
- b.Return(func);
- });
-
- auto* src = R"(
-SB = struct @align(16) {
- padding:vec4<f32> @offset(0)
- a:atomic<i32> @offset(16)
- b:atomic<u32> @offset(20)
-}
-
-$B1: { # root
- %v:ptr<storage, SB, read_write> = var @binding_point(0, 0)
-}
-
-%foo = @fragment func():void {
- $B2: {
- %3:ptr<storage, atomic<i32>, read_write> = access %v, 2u
- %4:i32 = atomicXor %3, 123i
- %x:i32 = let %4
- ret
- }
-}
-)";
- EXPECT_EQ(src, str());
-
- auto* expect = R"(
-)";
- Run(DecomposeStorageAccess);
-
- EXPECT_EQ(expect, str());
-}
-
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicExchange) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
- b.Let("x", b.Call(ty.i32(), core::BuiltinFn::kAtomicExchange,
- b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 2_u), 123_i));
- b.Return(func);
- });
-
- auto* src = R"(
-SB = struct @align(16) {
- padding:vec4<f32> @offset(0)
- a:atomic<i32> @offset(16)
- b:atomic<u32> @offset(20)
-}
-
-$B1: { # root
- %v:ptr<storage, SB, read_write> = var @binding_point(0, 0)
-}
-
-%foo = @fragment func():void {
- $B2: {
- %3:ptr<storage, atomic<i32>, read_write> = access %v, 2u
- %4:i32 = atomicExchange %3, 123i
- %x:i32 = let %4
- ret
- }
-}
-)";
- EXPECT_EQ(src, str());
-
- auto* expect = R"(
-)";
- Run(DecomposeStorageAccess);
-
- EXPECT_EQ(expect, str());
-}
-
-TEST_F(HlslWriterDecomposeStorageAccessTest, DISABLED_StorageAtomicCompareExchangeWeak) {
- auto* sb = ty.Struct(mod.symbols.New("SB"), {
- {mod.symbols.New("padding"), ty.vec4<f32>()},
- {mod.symbols.New("a"), ty.atomic<i32>()},
- {mod.symbols.New("b"), ty.atomic<u32>()},
- });
-
- auto* out = ty.Struct(
- mod.symbols.New("__atomic_compare_exchange_result"),
- {{mod.symbols.New("old_value"), ty.i32()}, {mod.symbols.New("exchanged"), ty.bool_()}});
-
- auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
- var->SetBindingPoint(0, 0);
- b.ir.root_block->Append(var);
-
- auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
- b.Append(func->Block(), [&] {
b.Let("x",
- b.Call(out, core::BuiltinFn::kAtomicCompareExchangeWeak,
+ b.Call(core::type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32()),
+ core::BuiltinFn::kAtomicCompareExchangeWeak,
b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i, 345_i));
b.Return(func);
});
@@ -1804,7 +1673,8 @@
a:atomic<i32> @offset(16)
b:atomic<u32> @offset(20)
}
-__atomic_compare_exchange_result = struct @align(4) {
+
+__atomic_compare_exchange_result_i32 = struct @align(4) {
old_value:i32 @offset(0)
exchanged:bool @offset(4)
}
@@ -1816,8 +1686,8 @@
%foo = @fragment func():void {
$B2: {
%3:ptr<storage, atomic<i32>, read_write> = access %v, 1u
- %4:__atomic_compare_exchange_result = atomicCompareExchangeWeak %3, 123i, 345i
- %x:__atomic_compare_exchange_result = let %4
+ %4:__atomic_compare_exchange_result_i32 = atomicCompareExchangeWeak %3, 123i, 345i
+ %x:__atomic_compare_exchange_result_i32 = let %4
ret
}
}
@@ -1825,12 +1695,243 @@
EXPECT_EQ(src, str());
auto* expect = R"(
+SB = struct @align(16) {
+ padding:vec4<f32> @offset(0)
+ a:atomic<i32> @offset(16)
+ b:atomic<u32> @offset(20)
+}
+
+__atomic_compare_exchange_result_i32 = struct @align(4) {
+ old_value:i32 @offset(0)
+ exchanged:bool @offset(4)
+}
+
+$B1: { # root
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = convert 16u
+ %5:void = %v.InterlockedCompareExchange %4, 123i, 345i, %3
+ %6:i32 = load %3
+ %7:bool = eq %6, 123i
+ %8:__atomic_compare_exchange_result_i32 = construct %6, %7
+ %x:__atomic_compare_exchange_result_i32 = let %8
+ ret
+ }
+}
)";
Run(DecomposeStorageAccess);
EXPECT_EQ(expect, str());
}
+TEST_F(HlslWriterDecomposeStorageAccessTest, StorageAtomicCompareExchangeWeakDirect) {
+ auto* var = b.Var("v", storage, ty.atomic<i32>(), core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("x", b.Call(core::type::CreateAtomicCompareExchangeResult(ty, mod.symbols, ty.i32()),
+ core::BuiltinFn::kAtomicCompareExchangeWeak, var, 123_i, 345_i));
+ b.Return(func);
+ });
+
+ auto* src = R"(
+__atomic_compare_exchange_result_i32 = struct @align(4) {
+ old_value:i32 @offset(0)
+ exchanged:bool @offset(4)
+}
+
+$B1: { # root
+ %v:ptr<storage, atomic<i32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:__atomic_compare_exchange_result_i32 = atomicCompareExchangeWeak %v, 123i, 345i
+ %x:__atomic_compare_exchange_result_i32 = let %3
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto* expect = R"(
+__atomic_compare_exchange_result_i32 = struct @align(4) {
+ old_value:i32 @offset(0)
+ exchanged:bool @offset(4)
+}
+
+$B1: { # root
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = convert 0u
+ %5:void = %v.InterlockedCompareExchange %4, 123i, 345i, %3
+ %6:i32 = load %3
+ %7:bool = eq %6, 123i
+ %8:__atomic_compare_exchange_result_i32 = construct %6, %7
+ %x:__atomic_compare_exchange_result_i32 = let %8
+ ret
+ }
+}
+)";
+ Run(DecomposeStorageAccess);
+
+ EXPECT_EQ(expect, str());
+}
+
+struct AtomicData {
+ core::BuiltinFn fn;
+ const char* atomic_name;
+ const char* interlock;
+};
+[[maybe_unused]] std::ostream& operator<<(std::ostream& out, const AtomicData& data) {
+ out << data.interlock;
+ return out;
+}
+using DecomposeBuiltinAtomic = core::ir::transform::TransformTestWithParam<AtomicData>;
+TEST_P(DecomposeBuiltinAtomic, IndirectAccess) {
+ auto params = GetParam();
+
+ auto* sb = ty.Struct(mod.symbols.New("SB"), {
+ {mod.symbols.New("padding"), ty.vec4<f32>()},
+ {mod.symbols.New("a"), ty.atomic<i32>()},
+ {mod.symbols.New("b"), ty.atomic<u32>()},
+ });
+
+ auto* var = b.Var("v", storage, sb, core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("x", b.Call(ty.i32(), params.fn,
+ b.Access(ty.ptr<storage, atomic<i32>, read_write>(), var, 1_u), 123_i));
+ b.Return(func);
+ });
+
+ auto src = R"(
+SB = struct @align(16) {
+ padding:vec4<f32> @offset(0)
+ a:atomic<i32> @offset(16)
+ b:atomic<u32> @offset(20)
+}
+
+$B1: { # root
+ %v:ptr<storage, SB, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<storage, atomic<i32>, read_write> = access %v, 1u
+ %4:i32 = )" +
+ std::string(params.atomic_name) + R"( %3, 123i
+ %x:i32 = let %4
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto expect = R"(
+SB = struct @align(16) {
+ padding:vec4<f32> @offset(0)
+ a:atomic<i32> @offset(16)
+ b:atomic<u32> @offset(20)
+}
+
+$B1: { # root
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<function, i32, read_write> = var, 0i
+ %4:i32 = convert 16u
+ %5:void = %v.)" +
+ std::string(params.interlock) + R"( %4, 123i, %3
+ %6:i32 = load %3
+ %x:i32 = let %6
+ ret
+ }
+}
+)";
+ Run(DecomposeStorageAccess);
+
+ EXPECT_EQ(expect, str());
+}
+
+TEST_P(DecomposeBuiltinAtomic, DirectAccess) {
+ auto param = GetParam();
+
+ auto* var = b.Var("v", storage, ty.atomic<u32>(), core::Access::kReadWrite);
+ var->SetBindingPoint(0, 0);
+ b.ir.root_block->Append(var);
+
+ auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+ b.Append(func->Block(), [&] {
+ b.Let("x", b.Call(ty.u32(), param.fn, var, 123_u));
+ b.Return(func);
+ });
+
+ auto src = R"(
+$B1: { # root
+ %v:ptr<storage, atomic<u32>, read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:u32 = )" +
+ std::string(param.atomic_name) + R"( %v, 123u
+ %x:u32 = let %3
+ ret
+ }
+}
+)";
+ EXPECT_EQ(src, str());
+
+ auto expect = R"(
+$B1: { # root
+ %v:hlsl.byte_address_buffer<read_write> = var @binding_point(0, 0)
+}
+
+%foo = @fragment func():void {
+ $B2: {
+ %3:ptr<function, u32, read_write> = var, 0u
+ %4:u32 = convert 0u
+ %5:void = %v.)" +
+ std::string(param.interlock) + R"( %4, 123u, %3
+ %6:u32 = load %3
+ %x:u32 = let %6
+ ret
+ }
+}
+)";
+ Run(DecomposeStorageAccess);
+
+ EXPECT_EQ(expect, str());
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ HlslWriterDecomposeStorageAccessTest,
+ DecomposeBuiltinAtomic,
+ testing::Values(AtomicData{core::BuiltinFn::kAtomicAdd, "atomicAdd", "InterlockedAdd"},
+ AtomicData{core::BuiltinFn::kAtomicMax, "atomicMax", "InterlockedMax"},
+ AtomicData{core::BuiltinFn::kAtomicMin, "atomicMin", "InterlockedMin"},
+ AtomicData{core::BuiltinFn::kAtomicAnd, "atomicAnd", "InterlockedAnd"},
+ AtomicData{core::BuiltinFn::kAtomicOr, "atomicOr", "InterlockedOr"},
+ AtomicData{core::BuiltinFn::kAtomicXor, "atomicXor", "InterlockedXor"},
+ AtomicData{core::BuiltinFn::kAtomicExchange, "atomicExchange",
+ "InterlockedExchange"}));
+
TEST_F(HlslWriterDecomposeStorageAccessTest, StoreVecF32) {
auto* var = b.Var<storage, vec4<f32>, core::Access::kReadWrite>("v");
var->SetBindingPoint(0, 0);