// 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
