blob: e911df34f86275938693b06551b9a95232da3767 [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/decompose_strided_array.h"
#include <memory>
#include <utility>
#include <vector>
#include "src/tint/program_builder.h"
#include "src/tint/transform/simplify_pointers.h"
#include "src/tint/transform/test_helper.h"
#include "src/tint/transform/unshadow.h"
using namespace tint::number_suffixes; // NOLINT
namespace tint::transform {
namespace {
using DecomposeStridedArrayTest = TransformTest;
TEST_F(DecomposeStridedArrayTest, ShouldRunEmptyModule) {
ProgramBuilder b;
EXPECT_FALSE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
}
TEST_F(DecomposeStridedArrayTest, ShouldRunNonStridedArray) {
// var<private> arr : array<f32, 4u>
ProgramBuilder b;
b.Global("arr", b.ty.array<f32, 4u>(), ast::StorageClass::kPrivate);
EXPECT_FALSE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
}
TEST_F(DecomposeStridedArrayTest, ShouldRunDefaultStridedArray) {
// var<private> arr : @stride(4) array<f32, 4u>
ProgramBuilder b;
b.Global("arr", b.ty.array<f32, 4u>(4), ast::StorageClass::kPrivate);
EXPECT_TRUE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
}
TEST_F(DecomposeStridedArrayTest, ShouldRunExplicitStridedArray) {
// var<private> arr : @stride(16) array<f32, 4u>
ProgramBuilder b;
b.Global("arr", b.ty.array<f32, 4u>(16), ast::StorageClass::kPrivate);
EXPECT_TRUE(ShouldRun<DecomposeStridedArray>(Program(std::move(b))));
}
TEST_F(DecomposeStridedArrayTest, Empty) {
auto* src = R"()";
auto* expect = src;
auto got = Run<DecomposeStridedArray>(src);
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, PrivateDefaultStridedArray) {
// var<private> arr : @stride(4) array<f32, 4u>
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a : @stride(4) array<f32, 4u> = a;
// let b : f32 = arr[1];
// }
ProgramBuilder b;
b.Global("arr", b.ty.array<f32, 4u>(4), ast::StorageClass::kPrivate);
b.Func("f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", b.ty.array<f32, 4u>(4), b.Expr("arr"))),
b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor("arr", 1_i))),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect = R"(
var<private> arr : array<f32, 4u>;
@stage(compute) @workgroup_size(1i)
fn f() {
let a : array<f32, 4u> = arr;
let b : f32 = arr[1i];
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, PrivateStridedArray) {
// var<private> arr : @stride(32) array<f32, 4u>
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a : @stride(32) array<f32, 4u> = a;
// let b : f32 = arr[1];
// }
ProgramBuilder b;
b.Global("arr", b.ty.array<f32, 4u>(32), ast::StorageClass::kPrivate);
b.Func("f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", b.ty.array<f32, 4u>(32), b.Expr("arr"))),
b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor("arr", 1_i))),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect = R"(
struct strided_arr {
@size(32)
el : f32,
}
var<private> arr : array<strided_arr, 4u>;
@stage(compute) @workgroup_size(1i)
fn f() {
let a : array<strided_arr, 4u> = arr;
let b : f32 = arr[1i].el;
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, ReadUniformStridedArray) {
// struct S {
// a : @stride(32) array<f32, 4u>,
// };
// @group(0) @binding(0) var<uniform> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a : @stride(32) array<f32, 4u> = s.a;
// let b : f32 = s.a[1];
// }
ProgramBuilder b;
auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(32))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform, b.GroupAndBinding(0, 0));
b.Func("f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", b.ty.array<f32, 4u>(32), b.MemberAccessor("s", "a"))),
b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i))),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect = R"(
struct strided_arr {
@size(32)
el : f32,
}
struct S {
a : array<strided_arr, 4u>,
}
@group(0) @binding(0) var<uniform> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
let a : array<strided_arr, 4u> = s.a;
let b : f32 = s.a[1i].el;
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, ReadUniformDefaultStridedArray) {
// struct S {
// a : @stride(16) array<vec4<f32>, 4u>,
// };
// @group(0) @binding(0) var<uniform> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a : @stride(16) array<vec4<f32>, 4u> = s.a;
// let b : f32 = s.a[1][2];
// }
ProgramBuilder b;
auto* S = b.Structure("S", {b.Member("a", b.ty.array(b.ty.vec4<f32>(), 4_u, 16))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kUniform, b.GroupAndBinding(0, 0));
b.Func(
"f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", b.ty.array(b.ty.vec4<f32>(), 4_u, 16), b.MemberAccessor("s", "a"))),
b.Decl(b.Let("b", b.ty.f32(),
b.IndexAccessor(b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i), 2_i))),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect =
R"(
struct S {
a : array<vec4<f32>, 4u>,
}
@group(0) @binding(0) var<uniform> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
let a : array<vec4<f32>, 4u> = s.a;
let b : f32 = s.a[1i][2i];
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, ReadStorageStridedArray) {
// struct S {
// a : @stride(32) array<f32, 4u>,
// };
// @group(0) @binding(0) var<storage> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a : @stride(32) array<f32, 4u> = s.a;
// let b : f32 = s.a[1];
// }
ProgramBuilder b;
auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(32))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage, b.GroupAndBinding(0, 0));
b.Func("f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", b.ty.array<f32, 4u>(32), b.MemberAccessor("s", "a"))),
b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i))),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect = R"(
struct strided_arr {
@size(32)
el : f32,
}
struct S {
a : array<strided_arr, 4u>,
}
@group(0) @binding(0) var<storage> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
let a : array<strided_arr, 4u> = s.a;
let b : f32 = s.a[1i].el;
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, ReadStorageDefaultStridedArray) {
// struct S {
// a : @stride(4) array<f32, 4u>,
// };
// @group(0) @binding(0) var<storage> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a : @stride(4) array<f32, 4u> = s.a;
// let b : f32 = s.a[1];
// }
ProgramBuilder b;
auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(4))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage, b.GroupAndBinding(0, 0));
b.Func("f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", b.ty.array<f32, 4u>(4), b.MemberAccessor("s", "a"))),
b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i))),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect = R"(
struct S {
a : array<f32, 4u>,
}
@group(0) @binding(0) var<storage> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
let a : array<f32, 4u> = s.a;
let b : f32 = s.a[1i];
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, WriteStorageStridedArray) {
// struct S {
// a : @stride(32) array<f32, 4u>,
// };
// @group(0) @binding(0) var<storage, read_write> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// s.a = @stride(32) array<f32, 4u>();
// s.a = @stride(32) array<f32, 4u>(1.0, 2.0, 3.0, 4.0);
// s.a[1i] = 5.0;
// }
ProgramBuilder b;
auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(32))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
b.GroupAndBinding(0, 0));
b.Func("f", {}, b.ty.void_(),
{
b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.array<f32, 4u>(32))),
b.Assign(b.MemberAccessor("s", "a"),
b.Construct(b.ty.array<f32, 4u>(32), 1_f, 2_f, 3_f, 4_f)),
b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i), 5_f),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect =
R"(
struct strided_arr {
@size(32)
el : f32,
}
struct S {
a : array<strided_arr, 4u>,
}
@group(0) @binding(0) var<storage, read_write> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
s.a = array<strided_arr, 4u>();
s.a = array<strided_arr, 4u>(strided_arr(1.0f), strided_arr(2.0f), strided_arr(3.0f), strided_arr(4.0f));
s.a[1i].el = 5.0f;
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, WriteStorageDefaultStridedArray) {
// struct S {
// a : @stride(4) array<f32, 4u>,
// };
// @group(0) @binding(0) var<storage, read_write> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// s.a = @stride(4) array<f32, 4u>();
// s.a = @stride(4) array<f32, 4u>(1.0, 2.0, 3.0, 4.0);
// s.a[1] = 5.0;
// }
ProgramBuilder b;
auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(4))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
b.GroupAndBinding(0, 0));
b.Func("f", {}, b.ty.void_(),
{
b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.array<f32, 4u>(4))),
b.Assign(b.MemberAccessor("s", "a"),
b.Construct(b.ty.array<f32, 4u>(4), 1_f, 2_f, 3_f, 4_f)),
b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i), 5_f),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect =
R"(
struct S {
a : array<f32, 4u>,
}
@group(0) @binding(0) var<storage, read_write> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
s.a = array<f32, 4u>();
s.a = array<f32, 4u>(1.0f, 2.0f, 3.0f, 4.0f);
s.a[1i] = 5.0f;
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, ReadWriteViaPointerLets) {
// struct S {
// a : @stride(32) array<f32, 4u>,
// };
// @group(0) @binding(0) var<storage, read_write> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a = &s.a;
// let b = &*&*(a);
// let c = *b;
// let d = (*b)[1];
// (*b) = @stride(32) array<f32, 4u>(1.0, 2.0, 3.0, 4.0);
// (*b)[1] = 5.0;
// }
ProgramBuilder b;
auto* S = b.Structure("S", {b.Member("a", b.ty.array<f32, 4u>(32))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
b.GroupAndBinding(0, 0));
b.Func("f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", nullptr, b.AddressOf(b.MemberAccessor("s", "a")))),
b.Decl(b.Let("b", nullptr, b.AddressOf(b.Deref(b.AddressOf(b.Deref("a")))))),
b.Decl(b.Let("c", nullptr, b.Deref("b"))),
b.Decl(b.Let("d", nullptr, b.IndexAccessor(b.Deref("b"), 1_i))),
b.Assign(b.Deref("b"), b.Construct(b.ty.array<f32, 4u>(32), 1_f, 2_f, 3_f, 4_f)),
b.Assign(b.IndexAccessor(b.Deref("b"), 1_i), 5_f),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect =
R"(
struct strided_arr {
@size(32)
el : f32,
}
struct S {
a : array<strided_arr, 4u>,
}
@group(0) @binding(0) var<storage, read_write> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
let c = s.a;
let d = s.a[1i].el;
s.a = array<strided_arr, 4u>(strided_arr(1.0f), strided_arr(2.0f), strided_arr(3.0f), strided_arr(4.0f));
s.a[1i].el = 5.0f;
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, PrivateAliasedStridedArray) {
// type ARR = @stride(32) array<f32, 4u>;
// struct S {
// a : ARR,
// };
// @group(0) @binding(0) var<storage, read_write> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a : ARR = s.a;
// let b : f32 = s.a[1];
// s.a = ARR();
// s.a = ARR(1.0, 2.0, 3.0, 4.0);
// s.a[1] = 5.0;
// }
ProgramBuilder b;
b.Alias("ARR", b.ty.array<f32, 4u>(32));
auto* S = b.Structure("S", {b.Member("a", b.ty.type_name("ARR"))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
b.GroupAndBinding(0, 0));
b.Func("f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", b.ty.type_name("ARR"), b.MemberAccessor("s", "a"))),
b.Decl(b.Let("b", b.ty.f32(), b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i))),
b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.type_name("ARR"))),
b.Assign(b.MemberAccessor("s", "a"),
b.Construct(b.ty.type_name("ARR"), 1_f, 2_f, 3_f, 4_f)),
b.Assign(b.IndexAccessor(b.MemberAccessor("s", "a"), 1_i), 5_f),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect = R"(
struct strided_arr {
@size(32)
el : f32,
}
type ARR = array<strided_arr, 4u>;
struct S {
a : ARR,
}
@group(0) @binding(0) var<storage, read_write> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
let a : ARR = s.a;
let b : f32 = s.a[1i].el;
s.a = ARR();
s.a = ARR(strided_arr(1.0f), strided_arr(2.0f), strided_arr(3.0f), strided_arr(4.0f));
s.a[1i].el = 5.0f;
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
TEST_F(DecomposeStridedArrayTest, PrivateNestedStridedArray) {
// type ARR_A = @stride(8) array<f32, 2u>;
// type ARR_B = @stride(128) array<@stride(16) array<ARR_A, 3u>, 4u>;
// struct S {
// a : ARR_B,
// };
// @group(0) @binding(0) var<storage, read_write> s : S;
//
// @stage(compute) @workgroup_size(1)
// fn f() {
// let a : ARR_B = s.a;
// let b : array<@stride(8) array<f32, 2u>, 3u> = s.a[3];
// let c = s.a[3][2];
// let d = s.a[3][2][1];
// s.a = ARR_B();
// s.a[3][2][1] = 5.0;
// }
ProgramBuilder b;
b.Alias("ARR_A", b.ty.array<f32, 2>(8));
b.Alias("ARR_B",
b.ty.array( //
b.ty.array(b.ty.type_name("ARR_A"), 3_u, 16), //
4_u, 128));
auto* S = b.Structure("S", {b.Member("a", b.ty.type_name("ARR_B"))});
b.Global("s", b.ty.Of(S), ast::StorageClass::kStorage, ast::Access::kReadWrite,
b.GroupAndBinding(0, 0));
b.Func("f", {}, b.ty.void_(),
{
b.Decl(b.Let("a", b.ty.type_name("ARR_B"), b.MemberAccessor("s", "a"))),
b.Decl(b.Let("b", b.ty.array(b.ty.type_name("ARR_A"), 3_u, 16),
b.IndexAccessor( //
b.MemberAccessor("s", "a"), //
3_i))),
b.Decl(b.Let("c", b.ty.type_name("ARR_A"),
b.IndexAccessor( //
b.IndexAccessor( //
b.MemberAccessor("s", "a"), //
3_i),
2_i))),
b.Decl(b.Let("d", b.ty.f32(),
b.IndexAccessor( //
b.IndexAccessor( //
b.IndexAccessor( //
b.MemberAccessor("s", "a"), //
3_i),
2_i),
1_i))),
b.Assign(b.MemberAccessor("s", "a"), b.Construct(b.ty.type_name("ARR_B"))),
b.Assign(b.IndexAccessor( //
b.IndexAccessor( //
b.IndexAccessor( //
b.MemberAccessor("s", "a"), //
3_i),
2_i),
1_i),
5_f),
},
{
b.Stage(ast::PipelineStage::kCompute),
b.WorkgroupSize(1_i),
});
auto* expect =
R"(
struct strided_arr {
@size(8)
el : f32,
}
type ARR_A = array<strided_arr, 2u>;
struct strided_arr_1 {
@size(128)
el : array<ARR_A, 3u>,
}
type ARR_B = array<strided_arr_1, 4u>;
struct S {
a : ARR_B,
}
@group(0) @binding(0) var<storage, read_write> s : S;
@stage(compute) @workgroup_size(1i)
fn f() {
let a : ARR_B = s.a;
let b : array<ARR_A, 3u> = s.a[3i].el;
let c : ARR_A = s.a[3i].el[2i];
let d : f32 = s.a[3i].el[2i][1i].el;
s.a = ARR_B();
s.a[3i].el[2i][1i].el = 5.0f;
}
)";
auto got = Run<Unshadow, SimplifyPointers, DecomposeStridedArray>(Program(std::move(b)));
EXPECT_EQ(expect, str(got));
}
} // namespace
} // namespace tint::transform