// 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_matrix.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_DecomposeStridedMatrixTest : public core::ir::transform::TransformTest {
  protected:
    void SetUp() override { capabilities.Add(core::ir::Capability::kAllowNonCoreTypes); }

    /// Create a struct that has a matrix member sandwiched between two u32 members, optionally
    /// nested inside one or more arrays.
    const core::type::Struct* Struct(const core::type::Matrix* matrix_type,
                                     uint32_t matrix_stride,
                                     std::initializer_list<uint32_t> array_counts = {}) {
        uint32_t member_size = matrix_stride * matrix_type->Columns();
        const core::type::Type* member_type = matrix_type;
        for (uint32_t count : array_counts) {
            member_type = ty.array(member_type, count);
            member_size *= count;
        }
        auto* matrix_member =
            ty.Get<core::type::StructMember>(mod.symbols.New("b"), member_type, 1u, matrix_stride,
                                             matrix_stride, member_size, core::IOAttributes{});
        matrix_member->SetMatrixStride(matrix_stride);
        return ty.Struct(mod.symbols.New("S"),
                         Vector{
                             ty.Get<core::type::StructMember>(mod.symbols.New("a"), ty.u32(), 0u,
                                                              0u, 4u, 4u, core::IOAttributes{}),
                             matrix_member,
                             ty.Get<core::type::StructMember>(mod.symbols.New("c"), ty.u32(), 2u,
                                                              matrix_member->Offset() + member_size,
                                                              4u, 4u, core::IOAttributes{}),
                         });
    }
};

TEST_F(SpirvReader_DecomposeStridedMatrixTest, NaturalStride_CreateConstant) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 16);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Composite(struct_type, 42_u, b.Zero(matrix_type), 42_u));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16), @matrix_stride(16)
  c:u32 @offset(80)
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B1: {
    %value:S = let S(42u, mat4x4<f32>(vec4<f32>(0.0f)), 42u)
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16), @matrix_stride(16)
  c:u32 @offset(80)
}

S_1 = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16)
  c:u32 @offset(80)
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B1: {
    %value:S_1 = let S_1(42u, mat4x4<f32>(vec4<f32>(0.0f)), 42u)
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, NaturalStride_LoadMatrix) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 16);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<private_, mat4x4<f32>>>(var, 1_u)));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16), @matrix_stride(16)
  c:u32 @offset(80)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 1u
    %4:mat4x4<f32> = load %3
    %value:mat4x4<f32> = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16), @matrix_stride(16)
  c:u32 @offset(80)
}

S_1 = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16)
  c:u32 @offset(80)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 1u
    %4:mat4x4<f32> = load %3
    %value:mat4x4<f32> = let %4
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, NaturalStride_ExtractMatrix) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 16);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* s = b.Let("s", b.Zero(struct_type));
        b.Let("value", b.Access<mat4x4<f32>>(s, 1_u));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16), @matrix_stride(16)
  c:u32 @offset(80)
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B1: {
    %s:S = let S(0u, mat4x4<f32>(vec4<f32>(0.0f)), 0u)
    %3:mat4x4<f32> = access %s, 1u
    %value:mat4x4<f32> = let %3
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16), @matrix_stride(16)
  c:u32 @offset(80)
}

S_1 = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16)
  c:u32 @offset(80)
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B1: {
    %s:S_1 = let S_1(0u, mat4x4<f32>(vec4<f32>(0.0f)), 0u)
    %3:mat4x4<f32> = access %s, 1u
    %value:mat4x4<f32> = let %3
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, NaturalStride_Construct) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 16);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* m = b.Let("m", b.Zero(matrix_type));
        b.Let("value", b.Construct(struct_type, 42_u, m, 42_u));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16), @matrix_stride(16)
  c:u32 @offset(80)
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B1: {
    %m:mat4x4<f32> = let mat4x4<f32>(vec4<f32>(0.0f))
    %3:S = construct 42u, %m, 42u
    %value:S = let %3
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16), @matrix_stride(16)
  c:u32 @offset(80)
}

S_1 = struct @align(16) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(16)
  c:u32 @offset(80)
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B1: {
    %m:mat4x4<f32> = let mat4x4<f32>(vec4<f32>(0.0f))
    %3:S_1 = construct 42u, %m, 42u
    %value:S_1 = let %3
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadMatrixElement) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* access = b.Access<ptr<private_, vec4<f32>>>(var, 1_u, 3_u);
        b.Let("value", b.LoadVectorElement(access, 2_u));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, vec4<f32>, read_write> = access %var, 1u, 3u
    %4:f32 = load_vector_element %3, 2u
    %value:f32 = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, vec4<f32>, read_write> = access %var, 1u, 3u
    %4:f32 = load_vector_element %3, 2u
    %value:f32 = let %4
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadMatrixColumn) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<private_, vec4<f32>>>(var, 1_u, 2_u)));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, vec4<f32>, read_write> = access %var, 1u, 2u
    %4:vec4<f32> = load %3
    %value:vec4<f32> = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, vec4<f32>, read_write> = access %var, 1u, 2u
    %4:vec4<f32> = load %3
    %value:vec4<f32> = let %4
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadMatrix) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<private_, mat4x4<f32>>>(var, 1_u)));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 1u
    %4:mat4x4<f32> = load %3
    %value:mat4x4<f32> = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %var, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = load %3
    %5:vec4<f32> = access %4, 0u
    %6:vec4<f32> = access %4, 1u
    %7:vec4<f32> = access %4, 2u
    %8:vec4<f32> = access %4, 3u
    %9:mat4x4<f32> = construct %5, %6, %7, %8
    %value:mat4x4<f32> = let %9
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadMatrix_ViaLet) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* let = b.Let("ptr", b.Access<ptr<private_, mat4x4<f32>>>(var, 1_u));
        b.Let("value", b.Load(let));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 1u
    %ptr:ptr<private, mat4x4<f32>, read_write> = let %3
    %5:mat4x4<f32> = load %ptr
    %value:mat4x4<f32> = let %5
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %var, 1u
    %ptr:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = let %3
    %5:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = load %ptr
    %6:vec4<f32> = access %5, 0u
    %7:vec4<f32> = access %5, 1u
    %8:vec4<f32> = access %5, 2u
    %9:vec4<f32> = access %5, 3u
    %10:mat4x4<f32> = construct %6, %7, %8, %9
    %value:mat4x4<f32> = let %10
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadStruct) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    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"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:S = load %var
    %value:S = let %3
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:S_1 = load %var
    %value:S_1 = let %3
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadStruct_ViaLet) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* let = b.Let("ptr", var);
        b.Let("value", b.Load(let));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %ptr:ptr<private, S, read_write> = let %var
    %4:S = load %ptr
    %value:S = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %ptr:ptr<private, S_1, read_write> = let %var
    %4:S_1 = load %ptr
    %value:S_1 = let %4
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadStruct_ExtractMatrix) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* struct_value = b.Let("struct_value", b.Load(var));
        b.Let("matrix_value", b.Access(matrix_type, struct_value, 1_u));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:S = load %var
    %struct_value:S = let %3
    %5:mat4x4<f32> = access %struct_value, 1u
    %matrix_value:mat4x4<f32> = let %5
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:S_1 = load %var
    %struct_value:S_1 = let %3
    %5:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %struct_value, 1u
    %6:vec4<f32> = access %5, 0u
    %7:vec4<f32> = access %5, 1u
    %8:vec4<f32> = access %5, 2u
    %9:vec4<f32> = access %5, 3u
    %10:mat4x4<f32> = construct %6, %7, %8, %9
    %matrix_value:mat4x4<f32> = let %10
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, StoreMatrixElement) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* access = b.Access<ptr<private_, vec4<f32>>>(var, 1_u, 3_u);
        b.StoreVectorElement(access, 2_u, 42_f);
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, vec4<f32>, read_write> = access %var, 1u, 3u
    store_vector_element %3, 2u, 42.0f
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, vec4<f32>, read_write> = access %var, 1u, 3u
    store_vector_element %3, 2u, 42.0f
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, StoreMatrixColumn) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Store(b.Access<ptr<private_, vec4<f32>>>(var, 1_u, 2_u), b.Zero<vec4<f32>>());
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, vec4<f32>, read_write> = access %var, 1u, 2u
    store %3, vec4<f32>(0.0f)
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, vec4<f32>, read_write> = access %var, 1u, 2u
    store %3, vec4<f32>(0.0f)
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, StoreMatrix) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Store(b.Access<ptr<private_, mat4x4<f32>>>(var, 1_u), b.Zero<mat4x4<f32>>());
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 1u
    store %3, mat4x4<f32>(vec4<f32>(0.0f))
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %var, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f)
    store %3, %4
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, StoreStruct) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Store(var, b.Zero(struct_type));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    store %var, S(0u, mat4x4<f32>(vec4<f32>(0.0f)), 0u)
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    store %var, S_1(0u, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>(vec4<f32>(0.0f)), 0u)
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadMatrixFromFuncParam) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* foo = b.Function("foo", ty.void_());
    auto* param = b.FunctionParam("param", ty.ptr(function, struct_type));
    foo->SetParams({param});
    b.Append(foo->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<function, mat4x4<f32>>>(param, 1_u)));
        b.Return(foo);
    });

    auto* bar = b.Function("bar", ty.void_());
    b.Append(bar->Block(), [&] {
        auto* var = b.Var("var", ty.ptr(function, struct_type));
        b.Call(foo, var);
        b.Return(bar);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

%foo = func(%param:ptr<function, S, read_write>):void {
  $B1: {
    %3:ptr<function, mat4x4<f32>, read_write> = access %param, 1u
    %4:mat4x4<f32> = load %3
    %value:mat4x4<f32> = let %4
    ret
  }
}
%bar = func():void {
  $B2: {
    %var:ptr<function, S, read_write> = var undef
    %8:void = call %foo, %var
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

%foo = func(%param:ptr<function, S_1, read_write>):void {
  $B1: {
    %3:ptr<function, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %param, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = load %3
    %5:vec4<f32> = access %4, 0u
    %6:vec4<f32> = access %4, 1u
    %7:vec4<f32> = access %4, 2u
    %8:vec4<f32> = access %4, 3u
    %9:mat4x4<f32> = construct %5, %6, %7, %8
    %value:mat4x4<f32> = let %9
    ret
  }
}
%bar = func():void {
  $B2: {
    %var:ptr<function, S_1, read_write> = var undef
    %13:void = call %foo, %var
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ReturnStructFromFunction) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* f = b.Function("foo", struct_type);
    b.Append(f->Block(), [&] {
        auto* var = b.Var("var", ty.ptr(function, struct_type));
        b.Return(f, b.Load(var));
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

%foo = func():S {
  $B1: {
    %var:ptr<function, S, read_write> = var undef
    %3:S = load %var
    ret %3
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

%foo = func():S_1 {
  $B1: {
    %var:ptr<function, S_1, read_write> = var undef
    %3:S_1 = load %var
    ret %3
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadMatrix_StructNestedInArray) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);
    auto* array_type = ty.array(struct_type, 4);

    auto* var = b.Var("var", ty.ptr<private_>(array_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<private_, mat4x4<f32>>>(var, 2_u, 1_u)));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, array<S, 4>, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 2u, 1u
    %4:mat4x4<f32> = load %3
    %value:mat4x4<f32> = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, array<S_1, 4>, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %var, 2u, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = load %3
    %5:vec4<f32> = access %4, 0u
    %6:vec4<f32> = access %4, 1u
    %7:vec4<f32> = access %4, 2u
    %8:vec4<f32> = access %4, 3u
    %9:mat4x4<f32> = construct %5, %6, %7, %8
    %value:mat4x4<f32> = let %9
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadMatrix_StructNestedInStridedArray) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);
    auto* strided_array = ty.Get<spirv::type::ExplicitLayoutArray>(
        struct_type, ty.Get<core::type::ConstantArrayCount>(4u), 1024u, 256u);

    auto* var = b.Var("var", ty.ptr<private_>(strided_array));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<private_, mat4x4<f32>>>(var, 2_u, 1_u)));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, spirv.explicit_layout_array<S, 4, stride=256>, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 2u, 1u
    %4:mat4x4<f32> = load %3
    %value:mat4x4<f32> = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

$B1: {  # root
  %var:ptr<private, spirv.explicit_layout_array<S_1, 4, stride=256>, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %var, 2u, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = load %3
    %5:vec4<f32> = access %4, 0u
    %6:vec4<f32> = access %4, 1u
    %7:vec4<f32> = access %4, 2u
    %8:vec4<f32> = access %4, 3u
    %9:mat4x4<f32> = construct %5, %6, %7, %8
    %value:mat4x4<f32> = let %9
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, LoadMatrix_StructNestedInStruct) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* inner_struct_type = Struct(matrix_type, 64);
    auto* outer_struct_type =
        ty.Struct(mod.symbols.New("Outer"), {
                                                {mod.symbols.New("a"), ty.u32()},
                                                {mod.symbols.New("b"), inner_struct_type},
                                            });

    auto* var = b.Var("var", ty.ptr<private_>(outer_struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<private_, mat4x4<f32>>>(var, 1_u, 1_u)));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

Outer = struct @align(64) {
  a_1:u32 @offset(0)
  b_1:S @offset(64)
}

$B1: {  # root
  %var:ptr<private, Outer, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 1u, 1u
    %4:mat4x4<f32> = load %3
    %value:mat4x4<f32> = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

Outer = struct @align(64) {
  a_1:u32 @offset(0)
  b_1:S @offset(64)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

Outer_1 = struct @align(64) {
  a_1:u32 @offset(0)
  b_1:S_1 @offset(64)
}

$B1: {  # root
  %var:ptr<private, Outer_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %var, 1u, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = load %3
    %5:vec4<f32> = access %4, 0u
    %6:vec4<f32> = access %4, 1u
    %7:vec4<f32> = access %4, 2u
    %8:vec4<f32> = access %4, 3u
    %9:mat4x4<f32> = construct %5, %6, %7, %8
    %value:mat4x4<f32> = let %9
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ConstructAndAccess) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64);

    auto* f = b.Function("foo", matrix_type);
    auto* p0 = b.FunctionParam("p0", ty.u32());
    auto* p1 = b.FunctionParam("p1", matrix_type);
    auto* p2 = b.FunctionParam("p2", ty.u32());
    f->SetParams({p0, p1, p2});

    b.Append(f->Block(), [&] {
        auto* c = b.Construct(struct_type, p0, p1, p2);
        auto* a = b.Access(matrix_type, c, 1_u);
        b.Return(f, a);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

%foo = func(%p0:u32, %p1:mat4x4<f32>, %p2:u32):mat4x4<f32> {
  $B1: {
    %5:S = construct %p0, %p1, %p2
    %6:mat4x4<f32> = access %5, 1u
    ret %6
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

%foo = func(%p0:u32, %p1:mat4x4<f32>, %p2:u32):mat4x4<f32> {
  $B1: {
    %5:vec4<f32> = access %p1, 0u
    %6:vec4<f32> = access %p1, 1u
    %7:vec4<f32> = access %p1, 2u
    %8:vec4<f32> = access %p1, 3u
    %9:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct %5, %6, %7, %8
    %10:S_1 = construct %p0, %9, %p2
    %11:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %10, 1u
    %12:vec4<f32> = access %11, 0u
    %13:vec4<f32> = access %11, 1u
    %14:vec4<f32> = access %11, 2u
    %15:vec4<f32> = access %11, 3u
    %16:mat4x4<f32> = construct %12, %13, %14, %15
    ret %16
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ExistingStridedArray) {
    auto* strided_array = ty.Get<spirv::type::ExplicitLayoutArray>(
        ty.vec4f(), ty.Get<core::type::ConstantArrayCount>(4u), 64u, 16u);
    auto* struct_ty =
        ty.Struct(mod.symbols.New("MyStruct"), {
                                                   {mod.symbols.New("a"), ty.u32()},
                                                   {mod.symbols.New("b"), strided_array},
                                               });

    auto* var = b.Var("var", ty.ptr<private_>(struct_ty));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* array_ptr = b.Access(ty.ptr(private_, strided_array), var, 1_u);
        b.Let("value", b.Load(array_ptr));
        b.Return(f);
    });

    auto* before = R"(
MyStruct = struct @align(16) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=16> @offset(16)
}

$B1: {  # root
  %var:ptr<private, MyStruct, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=16>, read_write> = access %var, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=16> = load %3
    %value:spirv.explicit_layout_array<vec4<f32>, 4, stride=16> = let %4
    ret
  }
}
)";
    auto* after = before;

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ArrayOfStridedMatrix_LoadMatrix) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64, {2, 3});

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<private_, mat4x4<f32>>>(var, 1_u, 2_u, 1_u)));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 1u, 2u, 1u
    %4:mat4x4<f32> = load %3
    %value:mat4x4<f32> = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> @offset(64) @size(1536)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %var, 1u, 2u, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = load %3
    %5:vec4<f32> = access %4, 0u
    %6:vec4<f32> = access %4, 1u
    %7:vec4<f32> = access %4, 2u
    %8:vec4<f32> = access %4, 3u
    %9:mat4x4<f32> = construct %5, %6, %7, %8
    %value:mat4x4<f32> = let %9
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ArrayOfStridedMatrix_LoadArray) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64, {2, 3});

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Let("value", b.Load(b.Access<ptr<private_, array<array<mat4x4<f32>, 2>, 3>>>(var, 1_u)));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, array<array<mat4x4<f32>, 2>, 3>, read_write> = access %var, 1u
    %4:array<array<mat4x4<f32>, 2>, 3> = load %3
    %value:array<array<mat4x4<f32>, 2>, 3> = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> @offset(64) @size(1536)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3>, read_write> = access %var, 1u
    %4:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> = load %3
    %5:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = access %4, 0u
    %6:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %5, 0u
    %7:vec4<f32> = access %6, 0u
    %8:vec4<f32> = access %6, 1u
    %9:vec4<f32> = access %6, 2u
    %10:vec4<f32> = access %6, 3u
    %11:mat4x4<f32> = construct %7, %8, %9, %10
    %12:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %5, 1u
    %13:vec4<f32> = access %12, 0u
    %14:vec4<f32> = access %12, 1u
    %15:vec4<f32> = access %12, 2u
    %16:vec4<f32> = access %12, 3u
    %17:mat4x4<f32> = construct %13, %14, %15, %16
    %18:array<mat4x4<f32>, 2> = construct %11, %17
    %19:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = access %4, 1u
    %20:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %19, 0u
    %21:vec4<f32> = access %20, 0u
    %22:vec4<f32> = access %20, 1u
    %23:vec4<f32> = access %20, 2u
    %24:vec4<f32> = access %20, 3u
    %25:mat4x4<f32> = construct %21, %22, %23, %24
    %26:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %19, 1u
    %27:vec4<f32> = access %26, 0u
    %28:vec4<f32> = access %26, 1u
    %29:vec4<f32> = access %26, 2u
    %30:vec4<f32> = access %26, 3u
    %31:mat4x4<f32> = construct %27, %28, %29, %30
    %32:array<mat4x4<f32>, 2> = construct %25, %31
    %33:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = access %4, 2u
    %34:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %33, 0u
    %35:vec4<f32> = access %34, 0u
    %36:vec4<f32> = access %34, 1u
    %37:vec4<f32> = access %34, 2u
    %38:vec4<f32> = access %34, 3u
    %39:mat4x4<f32> = construct %35, %36, %37, %38
    %40:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %33, 1u
    %41:vec4<f32> = access %40, 0u
    %42:vec4<f32> = access %40, 1u
    %43:vec4<f32> = access %40, 2u
    %44:vec4<f32> = access %40, 3u
    %45:mat4x4<f32> = construct %41, %42, %43, %44
    %46:array<mat4x4<f32>, 2> = construct %39, %45
    %47:array<array<mat4x4<f32>, 2>, 3> = construct %18, %32, %46
    %value:array<array<mat4x4<f32>, 2>, 3> = let %47
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ArrayOfStridedMatrix_StoreMatrix) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64, {2, 3});

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Store(b.Access<ptr<private_, mat4x4<f32>>>(var, 1_u, 2_u, 1_u), b.Zero<mat4x4<f32>>());
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, mat4x4<f32>, read_write> = access %var, 1u, 2u, 1u
    store %3, mat4x4<f32>(vec4<f32>(0.0f))
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> @offset(64) @size(1536)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %var, 1u, 2u, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f)
    store %3, %4
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ArrayOfStridedMatrix_StoreArray) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64, {2, 3});

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Store(b.Access<ptr<private_, array<array<mat4x4<f32>, 2>, 3>>>(var, 1_u),
                b.Zero<array<array<mat4x4<f32>, 2>, 3>>());
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, array<array<mat4x4<f32>, 2>, 3>, read_write> = access %var, 1u
    store %3, array<array<mat4x4<f32>, 2>, 3>(array<mat4x4<f32>, 2>(mat4x4<f32>(vec4<f32>(0.0f))))
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> @offset(64) @size(1536)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3>, read_write> = access %var, 1u
    %4:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f)
    %5:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f)
    %6:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = construct %4, %5
    %7:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f)
    %8:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f)
    %9:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = construct %7, %8
    %10:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f)
    %11:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f), vec4<f32>(0.0f)
    %12:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = construct %10, %11
    %13:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> = construct %6, %9, %12
    store %3, %13
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ArrayOfStridedMatrix_StoreStruct) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64, {2, 3});

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        b.Store(var, b.Zero(struct_type));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    store %var, S(0u, array<array<mat4x4<f32>, 2>, 3>(array<mat4x4<f32>, 2>(mat4x4<f32>(vec4<f32>(0.0f)))), 0u)
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> @offset(64) @size(1536)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    store %var, S_1(0u, array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3>(array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>(spirv.explicit_layout_array<vec4<f32>, 4, stride=64>(vec4<f32>(0.0f)))), 0u)
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ArrayOfStridedMatrix_ConstructAndAccess) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* array_type = ty.array(ty.array(matrix_type, 2), 3);
    auto* struct_type = Struct(matrix_type, 64, {2, 3});

    auto* f = b.Function("foo", array_type);
    auto* p0 = b.FunctionParam("p0", ty.u32());
    auto* p1 = b.FunctionParam("p1", array_type);
    auto* p2 = b.FunctionParam("p2", ty.u32());
    f->SetParams({p0, p1, p2});

    b.Append(f->Block(), [&] {
        auto* c = b.Construct(struct_type, p0, p1, p2);
        auto* a = b.Access(array_type, c, 1_u);
        b.Return(f, a);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

%foo = func(%p0:u32, %p1:array<array<mat4x4<f32>, 2>, 3>, %p2:u32):array<array<mat4x4<f32>, 2>, 3> {
  $B1: {
    %5:S = construct %p0, %p1, %p2
    %6:array<array<mat4x4<f32>, 2>, 3> = access %5, 1u
    ret %6
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> @offset(64) @size(1536)
  c:u32 @offset(1600)
}

%foo = func(%p0:u32, %p1:array<array<mat4x4<f32>, 2>, 3>, %p2:u32):array<array<mat4x4<f32>, 2>, 3> {
  $B1: {
    %5:array<mat4x4<f32>, 2> = access %p1, 0u
    %6:mat4x4<f32> = access %5, 0u
    %7:vec4<f32> = access %6, 0u
    %8:vec4<f32> = access %6, 1u
    %9:vec4<f32> = access %6, 2u
    %10:vec4<f32> = access %6, 3u
    %11:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct %7, %8, %9, %10
    %12:mat4x4<f32> = access %5, 1u
    %13:vec4<f32> = access %12, 0u
    %14:vec4<f32> = access %12, 1u
    %15:vec4<f32> = access %12, 2u
    %16:vec4<f32> = access %12, 3u
    %17:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct %13, %14, %15, %16
    %18:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = construct %11, %17
    %19:array<mat4x4<f32>, 2> = access %p1, 1u
    %20:mat4x4<f32> = access %19, 0u
    %21:vec4<f32> = access %20, 0u
    %22:vec4<f32> = access %20, 1u
    %23:vec4<f32> = access %20, 2u
    %24:vec4<f32> = access %20, 3u
    %25:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct %21, %22, %23, %24
    %26:mat4x4<f32> = access %19, 1u
    %27:vec4<f32> = access %26, 0u
    %28:vec4<f32> = access %26, 1u
    %29:vec4<f32> = access %26, 2u
    %30:vec4<f32> = access %26, 3u
    %31:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct %27, %28, %29, %30
    %32:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = construct %25, %31
    %33:array<mat4x4<f32>, 2> = access %p1, 2u
    %34:mat4x4<f32> = access %33, 0u
    %35:vec4<f32> = access %34, 0u
    %36:vec4<f32> = access %34, 1u
    %37:vec4<f32> = access %34, 2u
    %38:vec4<f32> = access %34, 3u
    %39:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct %35, %36, %37, %38
    %40:mat4x4<f32> = access %33, 1u
    %41:vec4<f32> = access %40, 0u
    %42:vec4<f32> = access %40, 1u
    %43:vec4<f32> = access %40, 2u
    %44:vec4<f32> = access %40, 3u
    %45:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = construct %41, %42, %43, %44
    %46:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = construct %39, %45
    %47:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> = construct %18, %32, %46
    %48:S_1 = construct %p0, %47, %p2
    %49:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> = access %48, 1u
    %50:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = access %49, 0u
    %51:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %50, 0u
    %52:vec4<f32> = access %51, 0u
    %53:vec4<f32> = access %51, 1u
    %54:vec4<f32> = access %51, 2u
    %55:vec4<f32> = access %51, 3u
    %56:mat4x4<f32> = construct %52, %53, %54, %55
    %57:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %50, 1u
    %58:vec4<f32> = access %57, 0u
    %59:vec4<f32> = access %57, 1u
    %60:vec4<f32> = access %57, 2u
    %61:vec4<f32> = access %57, 3u
    %62:mat4x4<f32> = construct %58, %59, %60, %61
    %63:array<mat4x4<f32>, 2> = construct %56, %62
    %64:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = access %49, 1u
    %65:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %64, 0u
    %66:vec4<f32> = access %65, 0u
    %67:vec4<f32> = access %65, 1u
    %68:vec4<f32> = access %65, 2u
    %69:vec4<f32> = access %65, 3u
    %70:mat4x4<f32> = construct %66, %67, %68, %69
    %71:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %64, 1u
    %72:vec4<f32> = access %71, 0u
    %73:vec4<f32> = access %71, 1u
    %74:vec4<f32> = access %71, 2u
    %75:vec4<f32> = access %71, 3u
    %76:mat4x4<f32> = construct %72, %73, %74, %75
    %77:array<mat4x4<f32>, 2> = construct %70, %76
    %78:array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2> = access %49, 2u
    %79:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %78, 0u
    %80:vec4<f32> = access %79, 0u
    %81:vec4<f32> = access %79, 1u
    %82:vec4<f32> = access %79, 2u
    %83:vec4<f32> = access %79, 3u
    %84:mat4x4<f32> = construct %80, %81, %82, %83
    %85:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = access %78, 1u
    %86:vec4<f32> = access %85, 0u
    %87:vec4<f32> = access %85, 1u
    %88:vec4<f32> = access %85, 2u
    %89:vec4<f32> = access %85, 3u
    %90:mat4x4<f32> = construct %86, %87, %88, %89
    %91:array<mat4x4<f32>, 2> = construct %84, %90
    %92:array<array<mat4x4<f32>, 2>, 3> = construct %63, %77, %91
    ret %92
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, ArrayOfStridedMatrix_LoadMatrix_AccessChain) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* struct_type = Struct(matrix_type, 64, {2, 3});

    auto* var = b.Var("var", ty.ptr<private_>(struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* outer_array_ptr =
            b.Access(ty.ptr(private_, ty.array(ty.array(matrix_type, 2), 3)), var, 1_u);
        auto* inner_array_ptr =
            b.Access(ty.ptr(private_, ty.array(matrix_type, 2)), outer_array_ptr, 2_u);
        auto* matrix_ptr = b.Access(ty.ptr(private_, matrix_type), inner_array_ptr, 1_u);
        b.Let("value", b.Load(matrix_ptr));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, array<array<mat4x4<f32>, 2>, 3>, read_write> = access %var, 1u
    %4:ptr<private, array<mat4x4<f32>, 2>, read_write> = access %3, 2u
    %5:ptr<private, mat4x4<f32>, read_write> = access %4, 1u
    %6:mat4x4<f32> = load %5
    %value:mat4x4<f32> = let %6
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<mat4x4<f32>, 2>, 3> @offset(64) @size(1536), @matrix_stride(64)
  c:u32 @offset(1600)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3> @offset(64) @size(1536)
  c:u32 @offset(1600)
}

$B1: {  # root
  %var:ptr<private, S_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, array<array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, 3>, read_write> = access %var, 1u
    %4:ptr<private, array<spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, 2>, read_write> = access %3, 2u
    %5:ptr<private, spirv.explicit_layout_array<vec4<f32>, 4, stride=64>, read_write> = access %4, 1u
    %6:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> = load %5
    %7:vec4<f32> = access %6, 0u
    %8:vec4<f32> = access %6, 1u
    %9:vec4<f32> = access %6, 2u
    %10:vec4<f32> = access %6, 3u
    %11:mat4x4<f32> = construct %7, %8, %9, %10
    %value:mat4x4<f32> = let %11
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, AccessNonMatrixPointer) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* inner_struct_type = Struct(matrix_type, 64);
    auto* outer_struct_type =
        ty.Struct(mod.symbols.New("Outer"), {
                                                {mod.symbols.New("s"), inner_struct_type},
                                            });

    auto* var = b.Var("var", ty.ptr<private_>(outer_struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* access = b.Access<ptr<private_, u32>>(var, 0_u, 2_u);
        b.Let("value", b.Load(access));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

Outer = struct @align(64) {
  s:S @offset(0)
}

$B1: {  # root
  %var:ptr<private, Outer, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, u32, read_write> = access %var, 0u, 2u
    %4:u32 = load %3
    %value:u32 = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

Outer = struct @align(64) {
  s:S @offset(0)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

Outer_1 = struct @align(64) {
  s:S_1 @offset(0)
}

$B1: {  # root
  %var:ptr<private, Outer_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:ptr<private, u32, read_write> = access %var, 0u, 2u
    %4:u32 = load %3
    %value:u32 = let %4
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

TEST_F(SpirvReader_DecomposeStridedMatrixTest, AccessNonMatrixValue) {
    auto* matrix_type = ty.mat4x4<f32>();
    auto* inner_struct_type = Struct(matrix_type, 64);
    auto* outer_struct_type =
        ty.Struct(mod.symbols.New("Outer"), {
                                                {mod.symbols.New("s"), inner_struct_type},
                                            });

    auto* var = b.Var("var", ty.ptr<private_>(outer_struct_type));
    mod.root_block->Append(var);

    auto* f = b.ComputeFunction("foo");
    b.Append(f->Block(), [&] {
        auto* struct_value = b.Load(var);
        b.Let("value", b.Access<u32>(struct_value, 0_u, 2_u));
        b.Return(f);
    });

    auto* before = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

Outer = struct @align(64) {
  s:S @offset(0)
}

$B1: {  # root
  %var:ptr<private, Outer, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:Outer = load %var
    %4:u32 = access %3, 0u, 2u
    %value:u32 = let %4
    ret
  }
}
)";
    auto* after = R"(
S = struct @align(64) {
  a:u32 @offset(0)
  b:mat4x4<f32> @offset(64) @size(256), @matrix_stride(64)
  c:u32 @offset(320)
}

Outer = struct @align(64) {
  s:S @offset(0)
}

S_1 = struct @align(64) {
  a:u32 @offset(0)
  b:spirv.explicit_layout_array<vec4<f32>, 4, stride=64> @offset(64)
  c:u32 @offset(320)
}

Outer_1 = struct @align(64) {
  s:S_1 @offset(0)
}

$B1: {  # root
  %var:ptr<private, Outer_1, read_write> = var undef
}

%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
  $B2: {
    %3:Outer_1 = load %var
    %4:u32 = access %3, 0u, 2u
    %value:u32 = let %4
    ret
  }
}
)";

    ASSERT_EQ(before, str());
    Run(DecomposeStridedMatrix);
    ASSERT_EQ(after, str());
}

}  // namespace
}  // namespace tint::spirv::reader::lower
