blob: 7f6db34e16f66ed1955802ee14ee4fbe0f806e62 [file] [log] [blame]
// Copyright 2022 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/tint/transform/spirv_atomic.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "src/tint/reader/wgsl/parser_impl.h"
#include "src/tint/transform/test_helper.h"
using namespace tint::number_suffixes; // NOLINT
namespace tint::transform {
namespace {
class SpirvAtomicTest : public TransformTest {
public:
Output Run(std::string in) {
auto file = std::make_unique<Source::File>("test", std::move(in));
auto parser = reader::wgsl::ParserImpl(file.get());
parser.Parse();
auto& b = parser.builder();
sem::BuiltinType two_params[] = {
sem::BuiltinType::kAtomicExchange, sem::BuiltinType::kAtomicAdd,
sem::BuiltinType::kAtomicSub, sem::BuiltinType::kAtomicMin,
sem::BuiltinType::kAtomicMax, sem::BuiltinType::kAtomicAnd,
sem::BuiltinType::kAtomicOr, sem::BuiltinType::kAtomicXor,
};
for (auto& a : two_params) {
b.Func(std::string{"stub_"} + sem::str(a) + "_u32",
utils::Vector{
b.Param("p0", b.ty.u32()),
b.Param("p1", b.ty.u32()),
},
b.ty.u32(),
utils::Vector{
b.Return(0_u),
},
utils::Vector{
b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(), a),
});
b.Func(std::string{"stub_"} + sem::str(a) + "_i32",
utils::Vector{
b.Param("p0", b.ty.i32()),
b.Param("p1", b.ty.i32()),
},
b.ty.i32(),
utils::Vector{
b.Return(0_i),
},
utils::Vector{
b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(), a),
});
}
b.Func("stub_atomicLoad_u32",
utils::Vector{
b.Param("p0", b.ty.u32()),
},
b.ty.u32(),
utils::Vector{
b.Return(0_u),
},
utils::Vector{
b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
sem::BuiltinType::kAtomicLoad),
});
b.Func("stub_atomicLoad_i32",
utils::Vector{
b.Param("p0", b.ty.i32()),
},
b.ty.i32(),
utils::Vector{
b.Return(0_i),
},
utils::Vector{
b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
sem::BuiltinType::kAtomicLoad),
});
b.Func("stub_atomicStore_u32",
utils::Vector{
b.Param("p0", b.ty.u32()),
b.Param("p1", b.ty.u32()),
},
b.ty.void_(), utils::Empty,
utils::Vector{
b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
sem::BuiltinType::kAtomicStore),
});
b.Func("stub_atomicStore_i32",
utils::Vector{
b.Param("p0", b.ty.i32()),
b.Param("p1", b.ty.i32()),
},
b.ty.void_(), utils::Empty,
utils::Vector{
b.ASTNodes().Create<SpirvAtomic::Stub>(b.ID(), b.AllocateNodeID(),
sem::BuiltinType::kAtomicStore),
});
b.Func("stub_atomic_compare_exchange_weak_u32",
utils::Vector{
b.Param("p0", b.ty.u32()),
b.Param("p1", b.ty.u32()),
b.Param("p2", b.ty.u32()),
},
b.ty.u32(),
utils::Vector{
b.Return(0_u),
},
utils::Vector{
b.ASTNodes().Create<SpirvAtomic::Stub>(
b.ID(), b.AllocateNodeID(), sem::BuiltinType::kAtomicCompareExchangeWeak),
});
b.Func("stub_atomic_compare_exchange_weak_i32",
utils::Vector{b.Param("p0", b.ty.i32()), b.Param("p1", b.ty.i32()),
b.Param("p2", b.ty.i32())},
b.ty.i32(),
utils::Vector{
b.Return(0_i),
},
utils::Vector{
b.ASTNodes().Create<SpirvAtomic::Stub>(
b.ID(), b.AllocateNodeID(), sem::BuiltinType::kAtomicCompareExchangeWeak),
});
// Keep this pointer alive after Transform() returns
files_.emplace_back(std::move(file));
return TransformTest::Run<SpirvAtomic>(Program(std::move(b)));
}
private:
std::vector<std::unique_ptr<Source::File>> files_;
};
TEST_F(SpirvAtomicTest, ArrayOfU32) {
auto* src = R"(
var<workgroup> wg : array<u32, 4>;
fn f() {
stub_atomicStore_u32(wg[1], 1u);
}
)";
auto* expect = R"(
var<workgroup> wg : array<atomic<u32>, 4u>;
fn f() {
atomicStore(&(wg[1]), 1u);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ArraysOfU32) {
auto* src = R"(
var<workgroup> wg : array<array<array<u32, 1>, 2>, 3>;
fn f() {
stub_atomicStore_u32(wg[2][1][0], 1u);
}
)";
auto* expect = R"(
var<workgroup> wg : array<array<array<atomic<u32>, 1u>, 2u>, 3u>;
fn f() {
atomicStore(&(wg[2][1][0]), 1u);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AliasedArraysOfU32) {
auto* src = R"(
type A0 = u32;
type A1 = array<A0, 1>;
type A2 = array<A1, 2>;
type A3 = array<A2, 3>;
var<workgroup> wg : A3;
fn f() {
stub_atomicStore_u32(wg[2][1][0], 1u);
}
)";
auto* expect = R"(
type A0 = u32;
type A1 = array<A0, 1>;
type A2 = array<A1, 2>;
type A3 = array<A2, 3>;
var<workgroup> wg : array<array<array<atomic<u32>, 1u>, 2u>, 3u>;
fn f() {
atomicStore(&(wg[2][1][0]), 1u);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, FlatStructSingleAtomic) {
auto* src = R"(
struct S {
a : u32,
}
var<workgroup> wg : S;
fn f() {
stub_atomicStore_u32(wg.a, 1u);
}
)";
auto* expect = R"(
struct S_atomic {
a : atomic<u32>,
}
struct S {
a : u32,
}
var<workgroup> wg : S_atomic;
fn f() {
atomicStore(&(wg.a), 1u);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, FlatStructMultipleAtomic) {
auto* src = R"(
struct S {
a : u32,
b : i32,
}
var<workgroup> wg : S;
fn f1() {
stub_atomicStore_u32(wg.a, 1u);
}
fn f2() {
stub_atomicStore_i32(wg.b, 2i);
}
)";
auto* expect = R"(
struct S_atomic {
a : atomic<u32>,
b : atomic<i32>,
}
struct S {
a : u32,
b : i32,
}
var<workgroup> wg : S_atomic;
fn f1() {
atomicStore(&(wg.a), 1u);
}
fn f2() {
atomicStore(&(wg.b), 2i);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, NestedStruct) {
auto* src = R"(
struct S0 {
a : u32,
b : i32,
c : u32,
}
struct S1 {
a : i32,
b : u32,
c : S0,
}
struct S2 {
a : i32,
b : S1,
c : u32,
}
var<workgroup> wg : S2;
fn f() {
stub_atomicStore_u32(wg.b.c.a, 1u);
}
)";
auto* expect = R"(
struct S0_atomic {
a : atomic<u32>,
b : i32,
c : u32,
}
struct S0 {
a : u32,
b : i32,
c : u32,
}
struct S1_atomic {
a : i32,
b : u32,
c : S0_atomic,
}
struct S1 {
a : i32,
b : u32,
c : S0,
}
struct S2_atomic {
a : i32,
b : S1_atomic,
c : u32,
}
struct S2 {
a : i32,
b : S1,
c : u32,
}
var<workgroup> wg : S2_atomic;
fn f() {
atomicStore(&(wg.b.c.a), 1u);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ArrayOfStruct) {
auto* src = R"(
struct S {
a : u32,
b : i32,
c : u32,
}
@group(0) @binding(1) var<storage, read_write> arr : array<S>;
fn f() {
stub_atomicStore_i32(arr[4].b, 1i);
}
)";
auto* expect = R"(
struct S_atomic {
a : u32,
b : atomic<i32>,
c : u32,
}
struct S {
a : u32,
b : i32,
c : u32,
}
@group(0) @binding(1) var<storage, read_write> arr : array<S_atomic>;
fn f() {
atomicStore(&(arr[4].b), 1i);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, StructOfArray) {
auto* src = R"(
struct S {
a : array<i32>,
}
@group(0) @binding(1) var<storage, read_write> s : S;
fn f() {
stub_atomicStore_i32(s.a[4], 1i);
}
)";
auto* expect = R"(
struct S_atomic {
a : array<atomic<i32>>,
}
struct S {
a : array<i32>,
}
@group(0) @binding(1) var<storage, read_write> s : S_atomic;
fn f() {
atomicStore(&(s.a[4]), 1i);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ViaPtrLet) {
auto* src = R"(
struct S {
i : i32,
}
@group(0) @binding(1) var<storage, read_write> s : S;
fn f() {
let p0 = &(s);
let p1 : ptr<storage, i32, read_write> = &((*(p0)).i);
stub_atomicStore_i32(*p1, 1i);
}
)";
auto* expect =
R"(
struct S_atomic {
i : atomic<i32>,
}
struct S {
i : i32,
}
@group(0) @binding(1) var<storage, read_write> s : S_atomic;
fn f() {
let p0 = &(s);
let p1 : ptr<storage, atomic<i32>, read_write> = &((*(p0)).i);
atomicStore(&(*(p1)), 1i);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, StructIsolatedMixedUsage) {
auto* src = R"(
struct S {
i : i32,
}
@group(0) @binding(1) var<storage, read_write> s : S;
fn f() {
stub_atomicStore_i32(s.i, 1i);
}
fn another_usage() {
var s : S;
let x : i32 = s.i;
s.i = 3i;
}
)";
auto* expect =
R"(
struct S_atomic {
i : atomic<i32>,
}
struct S {
i : i32,
}
@group(0) @binding(1) var<storage, read_write> s : S_atomic;
fn f() {
atomicStore(&(s.i), 1i);
}
fn another_usage() {
var s : S;
let x : i32 = s.i;
s.i = 3i;
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
// This sort of mixed usage isn't handled yet. Not sure if we need to just yet.
// If we don't, then the transform should give sensible diagnostics instead of producing invalid
// WGSL.
// TODO(crbug.com/tint/1595)
TEST_F(SpirvAtomicTest, DISABLED_StructComplexMixedUsage) {
auto* src = R"(
struct S {
i : i32,
}
@group(0) @binding(1) var<storage, read_write> s : S;
fn f() {
let x : i32 = s.i;
stub_atomicStore_i32(s.i, 1i);
s.i = 3i;
}
)";
auto* expect =
R"(
struct S_atomic {
i : atomic<i32>,
}
struct S {
i : i32,
}
@group(0) @binding(1) var<storage, read_write> s : S_atomic;
fn f() {
let x : i32 = atomicLoad(&s.i);
stub_atomicStore_i32(s.i, 1i);
atomicStore(&(s.i), 1i);
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicLoad) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicLoad_u32(wg_u32);}
{let r = stub_atomicLoad_i32(wg_i32);}
{let r = stub_atomicLoad_u32(sg_u32);}
{let r = stub_atomicLoad_i32(sg_i32);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicLoad(&(wg_u32));
}
{
let r = atomicLoad(&(wg_i32));
}
{
let r = atomicLoad(&(sg_u32));
}
{
let r = atomicLoad(&(sg_i32));
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicExchange) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicExchange_u32(wg_u32, 123u);}
{let r = stub_atomicExchange_i32(wg_i32, 123i);}
{let r = stub_atomicExchange_u32(sg_u32, 123u);}
{let r = stub_atomicExchange_i32(sg_i32, 123i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicExchange(&(wg_u32), 123u);
}
{
let r = atomicExchange(&(wg_i32), 123i);
}
{
let r = atomicExchange(&(sg_u32), 123u);
}
{
let r = atomicExchange(&(sg_i32), 123i);
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicAdd) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicAdd_u32(wg_u32, 123u);}
{let r = stub_atomicAdd_i32(wg_i32, 123i);}
{let r = stub_atomicAdd_u32(sg_u32, 123u);}
{let r = stub_atomicAdd_i32(sg_i32, 123i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicAdd(&(wg_u32), 123u);
}
{
let r = atomicAdd(&(wg_i32), 123i);
}
{
let r = atomicAdd(&(sg_u32), 123u);
}
{
let r = atomicAdd(&(sg_i32), 123i);
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicSub) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicSub_u32(wg_u32, 123u);}
{let r = stub_atomicSub_i32(wg_i32, 123i);}
{let r = stub_atomicSub_u32(sg_u32, 123u);}
{let r = stub_atomicSub_i32(sg_i32, 123i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicSub(&(wg_u32), 123u);
}
{
let r = atomicSub(&(wg_i32), 123i);
}
{
let r = atomicSub(&(sg_u32), 123u);
}
{
let r = atomicSub(&(sg_i32), 123i);
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicMin) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicMin_u32(wg_u32, 123u);}
{let r = stub_atomicMin_i32(wg_i32, 123i);}
{let r = stub_atomicMin_u32(sg_u32, 123u);}
{let r = stub_atomicMin_i32(sg_i32, 123i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicMin(&(wg_u32), 123u);
}
{
let r = atomicMin(&(wg_i32), 123i);
}
{
let r = atomicMin(&(sg_u32), 123u);
}
{
let r = atomicMin(&(sg_i32), 123i);
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicMax) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicMax_u32(wg_u32, 123u);}
{let r = stub_atomicMax_i32(wg_i32, 123i);}
{let r = stub_atomicMax_u32(sg_u32, 123u);}
{let r = stub_atomicMax_i32(sg_i32, 123i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicMax(&(wg_u32), 123u);
}
{
let r = atomicMax(&(wg_i32), 123i);
}
{
let r = atomicMax(&(sg_u32), 123u);
}
{
let r = atomicMax(&(sg_i32), 123i);
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicAnd) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicAnd_u32(wg_u32, 123u);}
{let r = stub_atomicAnd_i32(wg_i32, 123i);}
{let r = stub_atomicAnd_u32(sg_u32, 123u);}
{let r = stub_atomicAnd_i32(sg_i32, 123i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicAnd(&(wg_u32), 123u);
}
{
let r = atomicAnd(&(wg_i32), 123i);
}
{
let r = atomicAnd(&(sg_u32), 123u);
}
{
let r = atomicAnd(&(sg_i32), 123i);
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicOr) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicOr_u32(wg_u32, 123u);}
{let r = stub_atomicOr_i32(wg_i32, 123i);}
{let r = stub_atomicOr_u32(sg_u32, 123u);}
{let r = stub_atomicOr_i32(sg_i32, 123i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicOr(&(wg_u32), 123u);
}
{
let r = atomicOr(&(wg_i32), 123i);
}
{
let r = atomicOr(&(sg_u32), 123u);
}
{
let r = atomicOr(&(sg_i32), 123i);
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicXor) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomicXor_u32(wg_u32, 123u);}
{let r = stub_atomicXor_i32(wg_i32, 123i);}
{let r = stub_atomicXor_u32(sg_u32, 123u);}
{let r = stub_atomicXor_i32(sg_i32, 123i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let r = atomicXor(&(wg_u32), 123u);
}
{
let r = atomicXor(&(wg_i32), 123i);
}
{
let r = atomicXor(&(sg_u32), 123u);
}
{
let r = atomicXor(&(sg_i32), 123i);
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, AtomicCompareExchangeWeak) {
auto* src = R"(
var<workgroup> wg_u32 : u32;
var<workgroup> wg_i32 : i32;
@group(0) @binding(0) var<storage, read_write> sg_u32 : u32;
@group(0) @binding(1) var<storage, read_write> sg_i32 : i32;
fn f() {
{let r = stub_atomic_compare_exchange_weak_u32(wg_u32, 123u, 456u);}
{let r = stub_atomic_compare_exchange_weak_i32(wg_i32, 123i, 456i);}
{let r = stub_atomic_compare_exchange_weak_u32(sg_u32, 123u, 456u);}
{let r = stub_atomic_compare_exchange_weak_i32(sg_i32, 123i, 456i);}
}
)";
auto* expect =
R"(
var<workgroup> wg_u32 : atomic<u32>;
var<workgroup> wg_i32 : atomic<i32>;
@group(0) @binding(0) var<storage, read_write> sg_u32 : atomic<u32>;
@group(0) @binding(1) var<storage, read_write> sg_i32 : atomic<i32>;
fn f() {
{
let old_value = atomicCompareExchangeWeak(&(wg_u32), 123u, 456u).old_value;
let r = old_value;
}
{
let old_value_2 = atomicCompareExchangeWeak(&(wg_i32), 123i, 456i).old_value;
let r = old_value_2;
}
{
let old_value_1 = atomicCompareExchangeWeak(&(sg_u32), 123u, 456u).old_value;
let r = old_value_1;
}
{
let old_value_3 = atomicCompareExchangeWeak(&(sg_i32), 123i, 456i).old_value;
let r = old_value_3;
}
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ReplaceAssignsAndDecls_Scaler) {
auto* src = R"(
var<workgroup> wg : u32;
fn f() {
stub_atomicAdd_u32(wg, 1u);
wg = 0u;
let a = wg;
var b : u32;
b = wg;
}
)";
auto* expect = R"(
var<workgroup> wg : atomic<u32>;
fn f() {
atomicAdd(&(wg), 1u);
atomicStore(&(wg), 0u);
let a = atomicLoad(&(wg));
var b : u32;
b = atomicLoad(&(wg));
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ReplaceAssignsAndDecls_Struct) {
auto* src = R"(
struct S {
a : u32,
}
var<workgroup> wg : S;
fn f() {
stub_atomicAdd_u32(wg.a, 1u);
wg.a = 0u;
let a = wg.a;
var b : u32;
b = wg.a;
}
)";
auto* expect = R"(
struct S_atomic {
a : atomic<u32>,
}
struct S {
a : u32,
}
var<workgroup> wg : S_atomic;
fn f() {
atomicAdd(&(wg.a), 1u);
atomicStore(&(wg.a), 0u);
let a = atomicLoad(&(wg.a));
var b : u32;
b = atomicLoad(&(wg.a));
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ReplaceAssignsAndDecls_NestedStruct) {
auto* src = R"(
struct S0 {
a : u32,
}
struct S1 {
s0 : S0
}
var<workgroup> wg : S1;
fn f() {
stub_atomicAdd_u32(wg.s0.a, 1u);
wg.s0.a = 0u;
let a = wg.s0.a;
var b : u32;
b = wg.s0.a;
}
)";
auto* expect = R"(
struct S0_atomic {
a : atomic<u32>,
}
struct S0 {
a : u32,
}
struct S1_atomic {
s0 : S0_atomic,
}
struct S1 {
s0 : S0,
}
var<workgroup> wg : S1_atomic;
fn f() {
atomicAdd(&(wg.s0.a), 1u);
atomicStore(&(wg.s0.a), 0u);
let a = atomicLoad(&(wg.s0.a));
var b : u32;
b = atomicLoad(&(wg.s0.a));
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ReplaceAssignsAndDecls_StructMultipleAtomics) {
auto* src = R"(
struct S {
a : u32,
b : u32,
c : u32,
}
var<workgroup> wg : S;
fn f() {
stub_atomicAdd_u32(wg.a, 1u);
stub_atomicAdd_u32(wg.b, 1u);
wg.a = 0u;
let a = wg.a;
var b : u32;
b = wg.a;
wg.b = 0u;
let c = wg.b;
var d : u32;
d = wg.b;
wg.c = 0u;
let e = wg.c;
var f : u32;
f = wg.c;
}
)";
auto* expect = R"(
struct S_atomic {
a : atomic<u32>,
b : atomic<u32>,
c : u32,
}
struct S {
a : u32,
b : u32,
c : u32,
}
var<workgroup> wg : S_atomic;
fn f() {
atomicAdd(&(wg.a), 1u);
atomicAdd(&(wg.b), 1u);
atomicStore(&(wg.a), 0u);
let a = atomicLoad(&(wg.a));
var b : u32;
b = atomicLoad(&(wg.a));
atomicStore(&(wg.b), 0u);
let c = atomicLoad(&(wg.b));
var d : u32;
d = atomicLoad(&(wg.b));
wg.c = 0u;
let e = wg.c;
var f : u32;
f = wg.c;
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ReplaceAssignsAndDecls_ArrayOfScalar) {
auto* src = R"(
var<workgroup> wg : array<u32, 4>;
fn f() {
stub_atomicAdd_u32(wg[1], 1u);
wg[1] = 0u;
let a = wg[1];
var b : u32;
b = wg[1];
}
)";
auto* expect = R"(
var<workgroup> wg : array<atomic<u32>, 4u>;
fn f() {
atomicAdd(&(wg[1]), 1u);
atomicStore(&(wg[1]), 0u);
let a = atomicLoad(&(wg[1]));
var b : u32;
b = atomicLoad(&(wg[1]));
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ReplaceAssignsAndDecls_ArrayOfStruct) {
auto* src = R"(
struct S {
a : u32,
}
var<workgroup> wg : array<S, 4>;
fn f() {
stub_atomicAdd_u32(wg[1].a, 1u);
wg[1].a = 0u;
let a = wg[1].a;
var b : u32;
b = wg[1].a;
}
)";
auto* expect = R"(
struct S_atomic {
a : atomic<u32>,
}
struct S {
a : u32,
}
var<workgroup> wg : array<S_atomic, 4u>;
fn f() {
atomicAdd(&(wg[1].a), 1u);
atomicStore(&(wg[1].a), 0u);
let a = atomicLoad(&(wg[1].a));
var b : u32;
b = atomicLoad(&(wg[1].a));
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ReplaceAssignsAndDecls_StructOfArray) {
auto* src = R"(
struct S {
a : array<u32>,
}
@group(0) @binding(1) var<storage, read_write> s : S;
fn f() {
stub_atomicAdd_u32(s.a[4], 1u);
s.a[4] = 0u;
let a = s.a[4];
var b : u32;
b = s.a[4];
}
)";
auto* expect = R"(
struct S_atomic {
a : array<atomic<u32>>,
}
struct S {
a : array<u32>,
}
@group(0) @binding(1) var<storage, read_write> s : S_atomic;
fn f() {
atomicAdd(&(s.a[4]), 1u);
atomicStore(&(s.a[4]), 0u);
let a = atomicLoad(&(s.a[4]));
var b : u32;
b = atomicLoad(&(s.a[4]));
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(SpirvAtomicTest, ReplaceAssignsAndDecls_ViaPtrLet) {
auto* src = R"(
struct S {
i : u32,
}
@group(0) @binding(1) var<storage, read_write> s : S;
fn f() {
let p0 = &(s);
let p1 : ptr<storage, u32, read_write> = &((*(p0)).i);
stub_atomicAdd_u32(*p1, 1u);
*p1 = 0u;
let a = *p1;
var b : u32;
b = *p1;
}
)";
auto* expect = R"(
struct S_atomic {
i : atomic<u32>,
}
struct S {
i : u32,
}
@group(0) @binding(1) var<storage, read_write> s : S_atomic;
fn f() {
let p0 = &(s);
let p1 : ptr<storage, atomic<u32>, read_write> = &((*(p0)).i);
atomicAdd(&(*(p1)), 1u);
atomicStore(&(*(p1)), 0u);
let a = atomicLoad(&(*(p1)));
var b : u32;
b = atomicLoad(&(*(p1)));
}
)";
auto got = Run(src);
EXPECT_EQ(expect, str(got));
}
} // namespace
} // namespace tint::transform