blob: c4597d247773866236442e302b1a1a5dd07d9e58 [file] [log] [blame]
// Copyright 2025 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "src/tint/lang/spirv/reader/lower/decompose_strided_array.h"
#include "src/tint/lang/core/ir/transform/helper_test.h"
#include "src/tint/lang/spirv/type/explicit_layout_array.h"
namespace tint::spirv::reader::lower {
namespace {
using namespace tint::core::fluent_types; // NOLINT
using namespace tint::core::number_suffixes; // NOLINT
class SpirvReader_DecomposeStridedArrayTest : public core::ir::transform::TransformTest {
protected:
const spirv::type::ExplicitLayoutArray* Array(const core::type::Type* elem_ty,
uint32_t count,
uint32_t stride) {
if (stride == 0) {
stride = tint::RoundUp(elem_ty->Align(), elem_ty->Size());
}
return ty.Get<spirv::type::ExplicitLayoutArray>(
elem_ty, ty.Get<core::type::ConstantArrayCount>(static_cast<uint32_t>(count)),
stride * count, stride);
}
const spirv::type::ExplicitLayoutArray* RuntimeArray(const core::type::Type* elem_ty,
uint32_t stride) {
if (stride == 0) {
stride = tint::RoundUp(elem_ty->Align(), elem_ty->Size());
}
return ty.Get<spirv::type::ExplicitLayoutArray>(
elem_ty, ty.Get<core::type::RuntimeArrayCount>(), stride, stride);
}
};
TEST_F(SpirvReader_DecomposeStridedArrayTest, LoadElement) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Load(b.Access<ptr<storage, u32, read_write>>(var, 4_u)));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u
%4:u32 = load %3
%value:u32 = let %4
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u, 0u
%4:u32 = load %3
%value:u32 = let %4
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, LoadArray) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Load(var));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:spirv.explicit_layout_array<u32, 8, stride=16> = load %var
%value:spirv.explicit_layout_array<u32, 8, stride=16> = let %3
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:array<tint_padded_array_element, 8> = load %var
%value:array<tint_padded_array_element, 8> = let %3
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, StoreElement) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Store(b.Access<ptr<storage, u32, read_write>>(var, 4_u), 42_u);
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u
store %3, 42u
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u, 0u
store %3, 42u
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, StoreArray) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
auto* arr = b.Zero(array_type);
b.Store(var, arr);
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
store %var, spirv.explicit_layout_array<u32, 8, stride=16>(0u)
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
store %var, array<tint_padded_array_element, 8>(tint_padded_array_element(0u))
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, LoadElement_ViaLet) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
auto* let = b.Let("let", var);
b.Let("value", b.Load(b.Access<ptr<storage, u32, read_write>>(let, 4_u)));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%let:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = let %var
%4:ptr<storage, u32, read_write> = access %let, 4u
%5:u32 = load %4
%value:u32 = let %5
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%let:ptr<storage, array<tint_padded_array_element, 8>, read_write> = let %var
%4:ptr<storage, u32, read_write> = access %let, 4u, 0u
%5:u32 = load %4
%value:u32 = let %5
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, LoadElement_ViaAccessLet) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
auto* let = b.Let("let", b.Access<ptr<storage, u32, read_write>>(var, 4_u));
b.Let("value", b.Load(let));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u
%let:ptr<storage, u32, read_write> = let %3
%5:u32 = load %let
%value:u32 = let %5
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u, 0u
%let:ptr<storage, u32, read_write> = let %3
%5:u32 = load %let
%value:u32 = let %5
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, LoadElement_ViaFuncParam) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* callee = b.Function("callee", ty.u32());
auto* param = b.FunctionParam("param", ty.ptr(storage, array_type, read_write));
callee->SetParams({param});
b.Append(callee->Block(), [&] {
auto* access = b.Access<ptr<storage, u32, read_write>>(param, 4_u);
b.Return(callee, b.Load(access));
});
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Call(callee, var));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%callee = func(%param:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write>):u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %param, 4u
%5:u32 = load %4
ret %5
}
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B3: {
%7:u32 = call %callee, %var
%value:u32 = let %7
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
}
%callee = func(%param:ptr<storage, array<tint_padded_array_element, 8>, read_write>):u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %param, 4u, 0u
%5:u32 = load %4
ret %5
}
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B3: {
%7:u32 = call %callee, %var
%value:u32 = let %7
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, LoadArray_ViaFuncReturn) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* callee = b.Function("callee", array_type);
b.Append(callee->Block(), [&] { //
b.Return(callee, b.Load(var));
});
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Call(callee));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%callee = func():spirv.explicit_layout_array<u32, 8, stride=16> {
$B2: {
%3:spirv.explicit_layout_array<u32, 8, stride=16> = load %var
ret %3
}
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B3: {
%5:spirv.explicit_layout_array<u32, 8, stride=16> = call %callee
%value:spirv.explicit_layout_array<u32, 8, stride=16> = let %5
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
}
%callee = func():array<tint_padded_array_element, 8> {
$B2: {
%3:array<tint_padded_array_element, 8> = load %var
ret %3
}
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B3: {
%5:array<tint_padded_array_element, 8> = call %callee
%value:array<tint_padded_array_element, 8> = let %5
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, NestedArray_LoadInnerElement) {
auto* inner_array_type = Array(ty.u32(), 8, 16);
auto* outer_array_type = Array(inner_array_type, 8, 1024);
auto* var = b.Var("var", ty.ptr(storage, outer_array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Load(b.Access<ptr<storage, u32, read_write>>(var, 2_u, 3_u)));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<spirv.explicit_layout_array<u32, 8, stride=16>, 8, stride=1024>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 2u, 3u
%4:u32 = load %3
%value:u32 = let %4
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
tint_padded_array_element_1 = struct @align(4) {
tint_element_1:array<tint_padded_array_element, 8> @offset(0) @size(1024)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element_1, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 2u, 0u, 3u, 0u
%4:u32 = load %3
%value:u32 = let %4
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, NestedArray_LoadInnerArray) {
auto* inner_array_type = Array(ty.u32(), 8, 16);
auto* outer_array_type = Array(inner_array_type, 8, 1024);
auto* var = b.Var("var", ty.ptr(storage, outer_array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
auto* access = b.Access(ty.ptr(storage, inner_array_type, read_write), var, 2_u);
b.Let("value", b.Load(access));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<spirv.explicit_layout_array<u32, 8, stride=16>, 8, stride=1024>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = access %var, 2u
%4:spirv.explicit_layout_array<u32, 8, stride=16> = load %3
%value:spirv.explicit_layout_array<u32, 8, stride=16> = let %4
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
tint_padded_array_element_1 = struct @align(4) {
tint_element_1:array<tint_padded_array_element, 8> @offset(0) @size(1024)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element_1, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, array<tint_padded_array_element, 8>, read_write> = access %var, 2u, 0u
%4:array<tint_padded_array_element, 8> = load %3
%value:array<tint_padded_array_element, 8> = let %4
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, NestedArray_LoadOuterArray) {
auto* inner_array_type = Array(ty.u32(), 8, 16);
auto* outer_array_type = Array(inner_array_type, 8, 1024);
auto* var = b.Var("var", ty.ptr(storage, outer_array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Load(var));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<spirv.explicit_layout_array<u32, 8, stride=16>, 8, stride=1024>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:spirv.explicit_layout_array<spirv.explicit_layout_array<u32, 8, stride=16>, 8, stride=1024> = load %var
%value:spirv.explicit_layout_array<spirv.explicit_layout_array<u32, 8, stride=16>, 8, stride=1024> = let %3
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
tint_padded_array_element_1 = struct @align(4) {
tint_element_1:array<tint_padded_array_element, 8> @offset(0) @size(1024)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element_1, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:array<tint_padded_array_element_1, 8> = load %var
%value:array<tint_padded_array_element_1, 8> = let %3
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, NestedArray_LoadInnerElement_ViaFuncParam) {
auto* inner_array_type = Array(ty.u32(), 8, 16);
auto* outer_array_type = Array(inner_array_type, 8, 1024);
auto* var = b.Var("var", ty.ptr(storage, outer_array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* callee = b.Function("callee", ty.u32());
auto* param = b.FunctionParam("param", ty.ptr(storage, inner_array_type, read_write));
callee->SetParams({param});
b.Append(callee->Block(), [&] {
auto* access = b.Access<ptr<storage, u32, read_write>>(param, 4_u);
b.Return(callee, b.Load(access));
});
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
auto* access = b.Access(ty.ptr(storage, inner_array_type, read_write), var, 2_u);
b.Let("value", b.Call(callee, access));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<spirv.explicit_layout_array<u32, 8, stride=16>, 8, stride=1024>, read_write> = var undef @binding_point(0, 0)
}
%callee = func(%param:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write>):u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %param, 4u
%5:u32 = load %4
ret %5
}
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B3: {
%7:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = access %var, 2u
%8:u32 = call %callee, %7
%value:u32 = let %8
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
tint_padded_array_element_1 = struct @align(4) {
tint_element_1:array<tint_padded_array_element, 8> @offset(0) @size(1024)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element_1, 8>, read_write> = var undef @binding_point(0, 0)
}
%callee = func(%param:ptr<storage, array<tint_padded_array_element, 8>, read_write>):u32 {
$B2: {
%4:ptr<storage, u32, read_write> = access %param, 4u, 0u
%5:u32 = load %4
ret %5
}
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B3: {
%7:ptr<storage, array<tint_padded_array_element, 8>, read_write> = access %var, 2u, 0u
%8:u32 = call %callee, %7
%value:u32 = let %8
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, NestedArray_OuterIsNotExplicit) {
auto* inner_array_type = Array(ty.u32(), 8, 16);
auto* outer_array_type = ty.array(inner_array_type, 8);
auto* var = b.Var("var", ty.ptr(storage, outer_array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Load(b.Access<ptr<storage, u32, read_write>>(var, 2_u, 3_u)));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, array<spirv.explicit_layout_array<u32, 8, stride=16>, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 2u, 3u
%4:u32 = load %3
%value:u32 = let %4
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<array<tint_padded_array_element, 8>, 8>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 2u, 3u, 0u
%4:u32 = load %3
%value:u32 = let %4
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, Struct_LoadElement) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* struct_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.u32()},
{mod.symbols.New("b"), array_type},
});
auto* var = b.Var("var", ty.ptr(storage, struct_ty, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Load(b.Access<ptr<storage, u32, read_write>>(var, 1_u, 2_u)));
b.Return(f);
});
auto* before = R"(
MyStruct = struct @align(4) {
a:u32 @offset(0)
b:spirv.explicit_layout_array<u32, 8, stride=16> @offset(4)
}
$B1: { # root
%var:ptr<storage, MyStruct, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 1u, 2u
%4:u32 = load %3
%value:u32 = let %4
ret
}
}
)";
auto* after = R"(
MyStruct = struct @align(4) {
a:u32 @offset(0)
b:spirv.explicit_layout_array<u32, 8, stride=16> @offset(4)
}
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
MyStruct_1 = struct @align(4) {
a:u32 @offset(0)
b:array<tint_padded_array_element, 8> @offset(4)
}
$B1: { # root
%var:ptr<storage, MyStruct_1, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 1u, 2u, 0u
%4:u32 = load %3
%value:u32 = let %4
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, Struct_LoadWhole) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* struct_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.u32()},
{mod.symbols.New("b"), array_type},
});
auto* var = b.Var("var", ty.ptr(storage, struct_ty, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("value", b.Load(var));
b.Return(f);
});
auto* before = R"(
MyStruct = struct @align(4) {
a:u32 @offset(0)
b:spirv.explicit_layout_array<u32, 8, stride=16> @offset(4)
}
$B1: { # root
%var:ptr<storage, MyStruct, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:MyStruct = load %var
%value:MyStruct = let %3
ret
}
}
)";
auto* after = R"(
MyStruct = struct @align(4) {
a:u32 @offset(0)
b:spirv.explicit_layout_array<u32, 8, stride=16> @offset(4)
}
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
MyStruct_1 = struct @align(4) {
a:u32 @offset(0)
b:array<tint_padded_array_element, 8> @offset(4)
}
$B1: { # root
%var:ptr<storage, MyStruct_1, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:MyStruct_1 = load %var
%value:MyStruct_1 = let %3
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, Struct_StoreWhole) {
auto* array_type = Array(ty.u32(), 8, 16);
auto* struct_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.u32()},
{mod.symbols.New("b"), array_type},
});
auto* var = b.Var("var", ty.ptr(storage, struct_ty, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Store(var, b.Zero(struct_ty));
b.Return(f);
});
auto* before = R"(
MyStruct = struct @align(4) {
a:u32 @offset(0)
b:spirv.explicit_layout_array<u32, 8, stride=16> @offset(4)
}
$B1: { # root
%var:ptr<storage, MyStruct, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
store %var, MyStruct(0u, spirv.explicit_layout_array<u32, 8, stride=16>(0u))
ret
}
}
)";
auto* after = R"(
MyStruct = struct @align(4) {
a:u32 @offset(0)
b:spirv.explicit_layout_array<u32, 8, stride=16> @offset(4)
}
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
MyStruct_1 = struct @align(4) {
a:u32 @offset(0)
b:array<tint_padded_array_element, 8> @offset(4)
}
$B1: { # root
%var:ptr<storage, MyStruct_1, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
store %var, MyStruct_1(0u, array<tint_padded_array_element, 8>(tint_padded_array_element(0u)))
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, RuntimeArray) {
auto* array_type = RuntimeArray(ty.u32(), 16);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
auto* access = b.Access<ptr<storage, u32, read_write>>(var, 4_u);
b.Let("value", b.Load(access));
b.Store(access, b.Add(b.Load(access), 1_u));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, stride=16>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u
%4:u32 = load %3
%value:u32 = let %4
%6:u32 = load %3
%7:u32 = add %6, 1u
store %3, %7
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
$B1: { # root
%var:ptr<storage, array<tint_padded_array_element>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u, 0u
%4:u32 = load %3
%value:u32 = let %4
%6:u32 = load %3
%7:u32 = add %6, 1u
store %3, %7
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, MultipleVariables_DifferentElementTypes) {
auto* array_type_a = Array(ty.u32(), 8, 16);
auto* var_a = b.Var("a", ty.ptr(storage, array_type_a, read_write));
var_a->SetBindingPoint(0, 0);
mod.root_block->Append(var_a);
auto* array_type_b = Array(ty.i32(), 4, 8);
auto* var_b = b.Var("b", ty.ptr(storage, array_type_b, read_write));
var_b->SetBindingPoint(0, 1);
mod.root_block->Append(var_b);
auto* array_type_c = Array(ty.f32(), 2, 32);
auto* var_c = b.Var("c", ty.ptr(storage, array_type_c, read_write));
var_c->SetBindingPoint(0, 2);
mod.root_block->Append(var_c);
auto* array_type_d = Array(ty.vec4f(), 1, 16);
auto* var_d = b.Var("d", ty.ptr(storage, array_type_d, read_write));
var_d->SetBindingPoint(0, 3);
mod.root_block->Append(var_d);
auto* array_type_e = RuntimeArray(ty.vec2i(), 64);
auto* var_e = b.Var("e", ty.ptr(storage, array_type_e, read_write));
var_e->SetBindingPoint(0, 4);
mod.root_block->Append(var_e);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
b.Let("let_a", b.Load(b.Access<ptr<storage, u32, read_write>>(var_a, 1_u)));
b.Let("let_b", b.Load(b.Access<ptr<storage, i32, read_write>>(var_b, 2_u)));
b.Let("let_c", b.Load(b.Access<ptr<storage, f32, read_write>>(var_c, 0_u)));
b.Let("let_d", b.Load(b.Access<ptr<storage, vec4<f32>, read_write>>(var_d, 0_u)));
b.Let("let_e", b.Load(b.Access<ptr<storage, vec2<i32>, read_write>>(var_e, 3_u)));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%a:ptr<storage, spirv.explicit_layout_array<u32, 8, stride=16>, read_write> = var undef @binding_point(0, 0)
%b:ptr<storage, spirv.explicit_layout_array<i32, 4, stride=8>, read_write> = var undef @binding_point(0, 1)
%c:ptr<storage, spirv.explicit_layout_array<f32, 2, stride=32>, read_write> = var undef @binding_point(0, 2)
%d:ptr<storage, spirv.explicit_layout_array<vec4<f32>, 1, stride=16>, read_write> = var undef @binding_point(0, 3)
%e:ptr<storage, spirv.explicit_layout_array<vec2<i32>, stride=64>, read_write> = var undef @binding_point(0, 4)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%7:ptr<storage, u32, read_write> = access %a, 1u
%8:u32 = load %7
%let_a:u32 = let %8
%10:ptr<storage, i32, read_write> = access %b, 2u
%11:i32 = load %10
%let_b:i32 = let %11
%13:ptr<storage, f32, read_write> = access %c, 0u
%14:f32 = load %13
%let_c:f32 = let %14
%16:ptr<storage, vec4<f32>, read_write> = access %d, 0u
%17:vec4<f32> = load %16
%let_d:vec4<f32> = let %17
%19:ptr<storage, vec2<i32>, read_write> = access %e, 3u
%20:vec2<i32> = load %19
%let_e:vec2<i32> = let %20
ret
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
tint_padded_array_element_1 = struct @align(4) {
tint_element_1:i32 @offset(0) @size(8)
}
tint_padded_array_element_2 = struct @align(4) {
tint_element_2:f32 @offset(0) @size(32)
}
tint_padded_array_element_3 = struct @align(8) {
tint_element_3:vec2<i32> @offset(0) @size(64)
}
$B1: { # root
%a:ptr<storage, array<tint_padded_array_element, 8>, read_write> = var undef @binding_point(0, 0)
%b:ptr<storage, array<tint_padded_array_element_1, 4>, read_write> = var undef @binding_point(0, 1)
%c:ptr<storage, array<tint_padded_array_element_2, 2>, read_write> = var undef @binding_point(0, 2)
%d:ptr<storage, array<vec4<f32>, 1>, read_write> = var undef @binding_point(0, 3)
%e:ptr<storage, array<tint_padded_array_element_3>, read_write> = var undef @binding_point(0, 4)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%7:ptr<storage, u32, read_write> = access %a, 1u, 0u
%8:u32 = load %7
%let_a:u32 = let %8
%10:ptr<storage, i32, read_write> = access %b, 2u, 0u
%11:i32 = load %10
%let_b:i32 = let %11
%13:ptr<storage, f32, read_write> = access %c, 0u, 0u
%14:f32 = load %13
%let_c:f32 = let %14
%16:ptr<storage, vec4<f32>, read_write> = access %d, 0u
%17:vec4<f32> = load %16
%let_d:vec4<f32> = let %17
%19:ptr<storage, vec2<i32>, read_write> = access %e, 3u, 0u
%20:vec2<i32> = load %19
%let_e:vec2<i32> = let %20
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
// Test that a natural stride is not converted to a struct
TEST_F(SpirvReader_DecomposeStridedArrayTest, NaturalStride) {
auto* array_type = RuntimeArray(ty.u32(), 4);
auto* var = b.Var("var", ty.ptr(storage, array_type, read_write));
var->SetBindingPoint(0, 0);
mod.root_block->Append(var);
auto* f = b.ComputeFunction("foo");
b.Append(f->Block(), [&] {
auto* access = b.Access<ptr<storage, u32, read_write>>(var, 4_u);
b.Let("value", b.Load(access));
b.Store(access, b.Add(b.Load(access), 1_u));
b.Return(f);
});
auto* before = R"(
$B1: { # root
%var:ptr<storage, spirv.explicit_layout_array<u32, stride=4>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u
%4:u32 = load %3
%value:u32 = let %4
%6:u32 = load %3
%7:u32 = add %6, 1u
store %3, %7
ret
}
}
)";
auto* after = R"(
$B1: { # root
%var:ptr<storage, array<u32>, read_write> = var undef @binding_point(0, 0)
}
%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
$B2: {
%3:ptr<storage, u32, read_write> = access %var, 4u
%4:u32 = load %3
%value:u32 = let %4
%6:u32 = load %3
%7:u32 = add %6, 1u
store %3, %7
ret
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, PreserveIOAttributes) {
auto* array_type = Array(ty.f32(), 4, 4);
auto* struct_ty = ty.Struct(
mod.symbols.New("MyStruct"),
{
{mod.symbols.New("a"), ty.vec4f(), {.builtin = core::BuiltinValue::kPosition}},
{mod.symbols.New("b"), array_type, {.builtin = core::BuiltinValue::kClipDistances}},
});
auto* var = b.Var("var", ty.ptr(private_, struct_ty));
mod.root_block->Append(var);
auto* f = b.Function("foo", struct_ty, core::ir::Function::PipelineStage::kVertex);
b.Append(f->Block(), [&] {
b.Store(b.Access<ptr<private_, f32>>(var, 1_u, 2_u), 3_f);
b.Return(f, b.Load(var));
});
auto* before = R"(
MyStruct = struct @align(16) {
a:vec4<f32> @offset(0), @builtin(position)
b:spirv.explicit_layout_array<f32, 4, stride=4> @offset(16), @builtin(clip_distances)
}
$B1: { # root
%var:ptr<private, MyStruct, read_write> = var undef
}
%foo = @vertex func():MyStruct {
$B2: {
%3:ptr<private, f32, read_write> = access %var, 1u, 2u
store %3, 3.0f
%4:MyStruct = load %var
ret %4
}
}
)";
auto* after = R"(
MyStruct = struct @align(16) {
a:vec4<f32> @offset(0), @builtin(position)
b:spirv.explicit_layout_array<f32, 4, stride=4> @offset(16), @builtin(clip_distances)
}
MyStruct_1 = struct @align(16) {
a:vec4<f32> @offset(0), @builtin(position)
b:array<f32, 4> @offset(16), @builtin(clip_distances)
}
$B1: { # root
%var:ptr<private, MyStruct_1, read_write> = var undef
}
%foo = @vertex func():MyStruct_1 {
$B2: {
%3:ptr<private, f32, read_write> = access %var, 1u, 2u
store %3, 3.0f
%4:MyStruct_1 = load %var
ret %4
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, ConstructStridedArray) {
auto* array_type = Array(ty.u32(), 4, 16);
auto* f = b.Function("foo", ty.u32());
auto* e1 = b.FunctionParam("e1", ty.u32());
auto* e2 = b.FunctionParam("e2", ty.u32());
auto* e3 = b.FunctionParam("e3", ty.u32());
auto* e4 = b.FunctionParam("e4", ty.u32());
f->SetParams({e1, e2, e3, e4});
b.Append(f->Block(), [&] {
auto* arr = b.Let("arr", b.Construct(array_type, e1, e2, e3, e4));
auto* el = b.Let("el", b.Access<u32>(arr, 2_u));
b.Return(f, el);
});
auto* before = R"(
%foo = func(%e1:u32, %e2:u32, %e3:u32, %e4:u32):u32 {
$B1: {
%6:spirv.explicit_layout_array<u32, 4, stride=16> = construct %e1, %e2, %e3, %e4
%arr:spirv.explicit_layout_array<u32, 4, stride=16> = let %6
%8:u32 = access %arr, 2u
%el:u32 = let %8
ret %el
}
}
)";
auto* after = R"(
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
%foo = func(%e1:u32, %e2:u32, %e3:u32, %e4:u32):u32 {
$B1: {
%6:tint_padded_array_element = construct %e1
%7:tint_padded_array_element = construct %e2
%8:tint_padded_array_element = construct %e3
%9:tint_padded_array_element = construct %e4
%10:array<tint_padded_array_element, 4> = construct %6, %7, %8, %9
%arr:array<tint_padded_array_element, 4> = let %10
%12:u32 = access %arr, 2u, 0u
%el:u32 = let %12
ret %el
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
TEST_F(SpirvReader_DecomposeStridedArrayTest, ConstructStructOfStridedArray) {
auto* array_type = Array(ty.u32(), 4, 16);
auto* struct_ty = ty.Struct(mod.symbols.New("MyStruct"), {
{mod.symbols.New("a"), ty.u32()},
{mod.symbols.New("b"), array_type},
});
auto* f = b.Function("foo", ty.u32());
auto* e1 = b.FunctionParam("e1", ty.u32());
auto* e2 = b.FunctionParam("e2", ty.u32());
auto* e3 = b.FunctionParam("e3", ty.u32());
auto* e4 = b.FunctionParam("e4", ty.u32());
f->SetParams({e1, e2, e3, e4});
b.Append(f->Block(), [&] {
auto* arr =
b.Let("arr", b.Construct(struct_ty, 42_u, b.Construct(array_type, e1, e2, e3, e4)));
auto* el = b.Let("el", b.Access<u32>(arr, 1_u, 2_u));
b.Return(f, el);
});
auto* before = R"(
MyStruct = struct @align(4) {
a:u32 @offset(0)
b:spirv.explicit_layout_array<u32, 4, stride=16> @offset(4)
}
%foo = func(%e1:u32, %e2:u32, %e3:u32, %e4:u32):u32 {
$B1: {
%6:spirv.explicit_layout_array<u32, 4, stride=16> = construct %e1, %e2, %e3, %e4
%7:MyStruct = construct 42u, %6
%arr:MyStruct = let %7
%9:u32 = access %arr, 1u, 2u
%el:u32 = let %9
ret %el
}
}
)";
auto* after = R"(
MyStruct = struct @align(4) {
a:u32 @offset(0)
b:spirv.explicit_layout_array<u32, 4, stride=16> @offset(4)
}
tint_padded_array_element = struct @align(4) {
tint_element:u32 @offset(0) @size(16)
}
MyStruct_1 = struct @align(4) {
a:u32 @offset(0)
b:array<tint_padded_array_element, 4> @offset(4)
}
%foo = func(%e1:u32, %e2:u32, %e3:u32, %e4:u32):u32 {
$B1: {
%6:tint_padded_array_element = construct %e1
%7:tint_padded_array_element = construct %e2
%8:tint_padded_array_element = construct %e3
%9:tint_padded_array_element = construct %e4
%10:array<tint_padded_array_element, 4> = construct %6, %7, %8, %9
%11:MyStruct_1 = construct 42u, %10
%arr:MyStruct_1 = let %11
%13:u32 = access %arr, 1u, 2u, 0u
%el:u32 = let %13
ret %el
}
}
)";
ASSERT_EQ(before, str());
Run(DecomposeStridedArray);
ASSERT_EQ(after, str());
}
} // namespace
} // namespace tint::spirv::reader::lower