[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);