// Copyright 2021 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/transform/array_length_from_uniform.h" #include #include "src/transform/simplify_pointers.h" #include "src/transform/test_helper.h" #include "src/transform/unshadow.h" namespace tint { namespace transform { namespace { using ArrayLengthFromUniformTest = TransformTest; TEST_F(ArrayLengthFromUniformTest, ShouldRunEmptyModule) { auto* src = R"()"; EXPECT_FALSE(ShouldRun(src)); } TEST_F(ArrayLengthFromUniformTest, ShouldRunNoArrayLength) { auto* src = R"( struct SB { x : i32; arr : array; }; [[group(0), binding(0)]] var sb : SB; [[stage(compute), workgroup_size(1)]] fn main() { } )"; EXPECT_FALSE(ShouldRun(src)); } TEST_F(ArrayLengthFromUniformTest, ShouldRunWithArrayLength) { auto* src = R"( struct SB { x : i32; arr : array; }; [[group(0), binding(0)]] var sb : SB; [[stage(compute), workgroup_size(1)]] fn main() { var len : u32 = arrayLength(&sb.arr); } )"; EXPECT_TRUE(ShouldRun(src)); } TEST_F(ArrayLengthFromUniformTest, Error_MissingTransformData) { auto* src = R"( struct SB { x : i32; arr : array; }; [[group(0), binding(0)]] var sb : SB; [[stage(compute), workgroup_size(1)]] fn main() { var len : u32 = arrayLength(&sb.arr); } )"; auto* expect = "error: missing transform data for " "tint::transform::ArrayLengthFromUniform"; auto got = Run(src); EXPECT_EQ(expect, str(got)); } TEST_F(ArrayLengthFromUniformTest, Basic) { auto* src = R"( @group(0) @binding(0) var sb : array; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = arrayLength(&sb); } )"; auto* expect = R"( struct tint_symbol { buffer_size : array, 1u>; } @group(0) @binding(30) var tint_symbol_1 : tint_symbol; @group(0) @binding(0) var sb : array; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = (tint_symbol_1.buffer_size[0u][0u] / 4u); } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); EXPECT_EQ(std::unordered_set({0}), got.data.Get()->used_size_indices); } TEST_F(ArrayLengthFromUniformTest, BasicInStruct) { auto* src = R"( struct SB { x : i32; arr : array; }; @group(0) @binding(0) var sb : SB; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = arrayLength(&sb.arr); } )"; auto* expect = R"( struct tint_symbol { buffer_size : array, 1u>; } @group(0) @binding(30) var tint_symbol_1 : tint_symbol; struct SB { x : i32; arr : array; } @group(0) @binding(0) var sb : SB; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u); } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); EXPECT_EQ(std::unordered_set({0}), got.data.Get()->used_size_indices); } TEST_F(ArrayLengthFromUniformTest, WithStride) { auto* src = R"( @group(0) @binding(0) var sb : @stride(64) array; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = arrayLength(&sb); } )"; auto* expect = R"( struct tint_symbol { buffer_size : array, 1u>; } @group(0) @binding(30) var tint_symbol_1 : tint_symbol; @group(0) @binding(0) var sb : @stride(64) array; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = (tint_symbol_1.buffer_size[0u][0u] / 64u); } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); EXPECT_EQ(std::unordered_set({0}), got.data.Get()->used_size_indices); } TEST_F(ArrayLengthFromUniformTest, WithStride_InStruct) { auto* src = R"( struct SB { x : i32; y : f32; arr : @stride(64) array; }; @group(0) @binding(0) var sb : SB; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = arrayLength(&sb.arr); } )"; auto* expect = R"( struct tint_symbol { buffer_size : array, 1u>; } @group(0) @binding(30) var tint_symbol_1 : tint_symbol; struct SB { x : i32; y : f32; arr : @stride(64) array; } @group(0) @binding(0) var sb : SB; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 8u) / 64u); } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); EXPECT_EQ(std::unordered_set({0}), got.data.Get()->used_size_indices); } TEST_F(ArrayLengthFromUniformTest, MultipleStorageBuffers) { auto* src = R"( struct SB1 { x : i32; arr1 : array; }; struct SB2 { x : i32; arr2 : array>; }; struct SB4 { x : i32; arr4 : array>; }; @group(0) @binding(2) var sb1 : SB1; @group(1) @binding(2) var sb2 : SB2; @group(2) @binding(2) var sb3 : array>; @group(3) @binding(2) var sb4 : SB4; @group(4) @binding(2) var sb5 : array>; @stage(compute) @workgroup_size(1) fn main() { var len1 : u32 = arrayLength(&(sb1.arr1)); var len2 : u32 = arrayLength(&(sb2.arr2)); var len3 : u32 = arrayLength(&sb3); var len4 : u32 = arrayLength(&(sb4.arr4)); var len5 : u32 = arrayLength(&sb5); var x : u32 = (len1 + len2 + len3 + len4 + len5); } )"; auto* expect = R"( struct tint_symbol { buffer_size : array, 2u>; } @group(0) @binding(30) var tint_symbol_1 : tint_symbol; struct SB1 { x : i32; arr1 : array; } struct SB2 { x : i32; arr2 : array>; } struct SB4 { x : i32; arr4 : array>; } @group(0) @binding(2) var sb1 : SB1; @group(1) @binding(2) var sb2 : SB2; @group(2) @binding(2) var sb3 : array>; @group(3) @binding(2) var sb4 : SB4; @group(4) @binding(2) var sb5 : array>; @stage(compute) @workgroup_size(1) fn main() { var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u); var len2 : u32 = ((tint_symbol_1.buffer_size[0u][1u] - 16u) / 16u); var len3 : u32 = (tint_symbol_1.buffer_size[0u][2u] / 16u); var len4 : u32 = ((tint_symbol_1.buffer_size[0u][3u] - 16u) / 16u); var len5 : u32 = (tint_symbol_1.buffer_size[1u][0u] / 16u); var x : u32 = ((((len1 + len2) + len3) + len4) + len5); } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2u}, 0); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{1u, 2u}, 1); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{2u, 2u}, 2); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{3u, 2u}, 3); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{4u, 2u}, 4); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); EXPECT_EQ(std::unordered_set({0, 1, 2, 3, 4}), got.data.Get()->used_size_indices); } TEST_F(ArrayLengthFromUniformTest, MultipleUnusedStorageBuffers) { auto* src = R"( struct SB1 { x : i32; arr1 : array; }; struct SB2 { x : i32; arr2 : array>; }; struct SB4 { x : i32; arr4 : array>; }; @group(0) @binding(2) var sb1 : SB1; @group(1) @binding(2) var sb2 : SB2; @group(2) @binding(2) var sb3 : array>; @group(3) @binding(2) var sb4 : SB4; @group(4) @binding(2) var sb5 : array>; @stage(compute) @workgroup_size(1) fn main() { var len1 : u32 = arrayLength(&(sb1.arr1)); var len3 : u32 = arrayLength(&sb3); var x : u32 = (len1 + len3); } )"; auto* expect = R"( struct tint_symbol { buffer_size : array, 1u>; } @group(0) @binding(30) var tint_symbol_1 : tint_symbol; struct SB1 { x : i32; arr1 : array; } struct SB2 { x : i32; arr2 : array>; } struct SB4 { x : i32; arr4 : array>; } @group(0) @binding(2) var sb1 : SB1; @group(1) @binding(2) var sb2 : SB2; @group(2) @binding(2) var sb3 : array>; @group(3) @binding(2) var sb4 : SB4; @group(4) @binding(2) var sb5 : array>; @stage(compute) @workgroup_size(1) fn main() { var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u); var len3 : u32 = (tint_symbol_1.buffer_size[0u][2u] / 16u); var x : u32 = (len1 + len3); } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2u}, 0); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{1u, 2u}, 1); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{2u, 2u}, 2); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{3u, 2u}, 3); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{4u, 2u}, 4); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); EXPECT_EQ(std::unordered_set({0, 2}), got.data.Get()->used_size_indices); } TEST_F(ArrayLengthFromUniformTest, NoArrayLengthCalls) { auto* src = R"( struct SB { x : i32; arr : array; } @group(0) @binding(0) var sb : SB; @stage(compute) @workgroup_size(1) fn main() { _ = &(sb.arr); } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(src, str(got)); EXPECT_EQ(got.data.Get(), nullptr); } TEST_F(ArrayLengthFromUniformTest, MissingBindingPointToIndexMapping) { auto* src = R"( struct SB1 { x : i32; arr1 : array; }; struct SB2 { x : i32; arr2 : array>; }; @group(0) @binding(2) var sb1 : SB1; @group(1) @binding(2) var sb2 : SB2; @stage(compute) @workgroup_size(1) fn main() { var len1 : u32 = arrayLength(&(sb1.arr1)); var len2 : u32 = arrayLength(&(sb2.arr2)); var x : u32 = (len1 + len2); } )"; auto* expect = R"( struct tint_symbol { buffer_size : array, 1u>; } @group(0) @binding(30) var tint_symbol_1 : tint_symbol; struct SB1 { x : i32; arr1 : array; } struct SB2 { x : i32; arr2 : array>; } @group(0) @binding(2) var sb1 : SB1; @group(1) @binding(2) var sb2 : SB2; @stage(compute) @workgroup_size(1) fn main() { var len1 : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u); var len2 : u32 = arrayLength(&(sb2.arr2)); var x : u32 = (len1 + len2); } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 2}, 0); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); EXPECT_EQ(std::unordered_set({0}), got.data.Get()->used_size_indices); } TEST_F(ArrayLengthFromUniformTest, OutOfOrder) { auto* src = R"( @stage(compute) @workgroup_size(1) fn main() { var len : u32 = arrayLength(&sb.arr); } @group(0) @binding(0) var sb : SB; struct SB { x : i32; arr : array; }; )"; auto* expect = R"( struct tint_symbol { buffer_size : array, 1u>; } @group(0) @binding(30) var tint_symbol_1 : tint_symbol; @stage(compute) @workgroup_size(1) fn main() { var len : u32 = ((tint_symbol_1.buffer_size[0u][0u] - 4u) / 4u); } @group(0) @binding(0) var sb : SB; struct SB { x : i32; arr : array; } )"; ArrayLengthFromUniform::Config cfg({0, 30u}); cfg.bindpoint_to_size_index.emplace(sem::BindingPoint{0, 0}, 0); DataMap data; data.Add(std::move(cfg)); auto got = Run(src, data); EXPECT_EQ(expect, str(got)); EXPECT_EQ(std::unordered_set({0}), got.data.Get()->used_size_indices); } } // namespace } // namespace transform } // namespace tint